df <- select(df, -(X))
pal1 <- wes_palette(24, name = "Zissou1", type = "continuous")
df$Job.Title <- str_extract(df$Job.Title, "[^-]+")
df$Job.Title <- str_extract(df$Job.Title, "[^/]+")
df$Job.Title <- str_extract(df$Job.Title, "[^,]+")
df <- filter(df, Job.Title != "E")
df <- filter(df, Job.Title != "AI")
df <- filter(df, Job.Title != "In")
df <- filter(df, Job.Title != "VP")
df <- filter(df, Job.Title != "Hydrogen")
df$Job.Title <- gsub("Senior", "", df$Job.Title)
df$Job.Title <- gsub("Sr.", "", df$Job.Title)
df$Job.Title <- gsub("Staff", "", df$Job.Title)
df$Job.Title[grepl('Analyst',df$Job.Title)] <- 'Data Analyst'
df$Job.Title[grepl('Scientist',df$Job.Title)] <- 'Data Scientist'
df$Job.Title[grepl('SCIENTIST',df$Job.Title)] <- 'Data Scientist'
df$Job.Title[grepl('Data Science',df$Job.Title)] <- 'Data Scientist'
df$Job.Title[grepl('Data Engineer',df$Job.Title)] <- 'Data Engineer'
df$Job.Title[grepl('ENGINEER',df$Job.Title)] <- 'Data Engineer'
df$Job.Title[grepl('Modeler',df$Job.Title)] <- 'Data Modeler'
df$Job.Title[grepl('Analytics',df$Job.Title)] <- 'Analytics'
df$Job.Title[grepl('Manager ',df$Job.Title)] <- 'Manager'
df$Job.Title[grepl('ML',df$Job.Title)] <- 'Machine Learning Engineer'
df %>%
select(Job.Title, max_salary) %>%
group_by(Job.Title) %>%
summarise(mean = mean(max_salary)) %>%
ggplot(aes(x = reorder(Job.Title, mean), y = mean, fill = Job.Title)) +
geom_col() +
coord_flip() +
labs(x = "Job Title", y = "Average Salary (in thousands of $)", caption = "Source: 2018 Kaggle Data Science Job Posting on Glassdor") +
ggtitle("Average salary by Job") +
scale_fill_manual(values=pal1) +
theme_minimal() +
theme(legend.title=element_blank(),
legend.position = "none",
plot.caption = element_text(hjust = -1.3, vjust = -2.3,size = 8, color ="azure4"),
plot.title = element_text(hjust = -0.4, vjust= 2.5),
axis.title.x = element_text(vjust = -0.5),
axis.title.y = element_text(vjust = 3))

The aim of our project is to investigate Data Science Jobs in the US. We are interested in the topic since we are studying to become Data Scientists, and therefore, would like to know more about it. We are going to use the data about Data Science Job Posting on Glassdoor. It was collected by web-scraping job posts from Glassdoor for data science jobs.
We have decided to start our analysis with a graph that could give a sort of general overview of Data Science Jobs (and related) and salaries. From the graph above, it can be seen that being a Manager is definitely the position that, on average, pays the most. Data Architect immediately follows with an average yearly salary of $200,000. Contrary to our expectation, Data Modeler does not earn that much and can be found at almost the end of the ranking.
Data Science Salaries by Sector
In this part of the project, we are going to analyze which sectors have the highest salaries. We think this is a useful because it highlights the trend that, even in the field of data science, there can be significant differences in salary depending on the field that an individual may choose to go into.
# install.packages("wesanderson")
library(ggthemes)
library(plotly)
library(wesanderson)
library(ggrepel)
pal <- wes_palette(22, name = "FantasticFox1", type = "continuous")
pal <- wes_palette(22, name = "FantasticFox1", type = "continuous")
d1 <- df %>% filter(!Sector=="-1") %>% group_by(Sector) %>% summarise(Job_Openings=n(), Average_Salary=mean(avg_salary)) %>% filter(Job_Openings >3) %>% ggplot() +
geom_col(aes(x = reorder(Sector, Average_Salary), y = round(Average_Salary), fill=Sector, text=paste0("Average Salary:" ,round(Average_Salary)," 000s USD")), size = 1, color = "white") +
geom_line(aes(x = reorder(Sector,Average_Salary), y = Job_Openings), size = 0.5, color="gray35", group = 1)+
geom_point(aes(x = reorder(Sector,Average_Salary), y = Job_Openings, text=paste("Number of Job Postings: ",Job_Openings)))+labs(x="Sectors", y="Average Annual Salary (in thousands of $)")+theme_tufte()+guides(color = guide_legend(override.aes = list(size = 0.1))) +theme(legend.position="bottom") +theme(legend.title = element_text(size = 5), legend.text = element_text(size = 5))+
theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank())+
theme(plot.title = element_text(size = 10, face = "bold", hjust = 0.5))+
theme(axis.text.x = element_blank())+
scale_fill_manual(values=pal)
#Below turns d1 visualization into interactive plotly graph
ggplotly(d1, tooltip="text") %>%
layout(title = list(text = paste0('Data Related Job Posting Salaries per Sector',
'<br>',
'<sup>',
'Covers job titles such as data scientist, data analyst, data engineer...',
'<br>',
'<sup>',
'Hover over the black line to see number of job openings per sector.',
'</sup>')), size=I(3),titlefont = list(size = 13))
NA
The above graph shows the average salary for data-related job postings across different sectors. Highest paying sectors are Media and Retail. However, the total number of openings from those sectors in the data set are only 5 and 7 respectively. The sector with the highest opportunity for data related jobs seem to be Business Services and Information Technology, because, despite being on the average salary range across sectors, they have the highest number of openings and the biggest job opportunity.
Interest in Data Science Jobs and Available Jobs per State
After exploring the relationship between sectors and salaries, we next try to assess the popularity of data science roles, and how it differentiates by state. We are assuming that Google search trends for data science related search terms (such as data modeler, data architect and data engineer) are an effective proxy for interest. We then compare how interest in data science roles, as measured by Google trends, is related to the actual number of job openings. For this purpose, we pulled Google Search trends for the following job titles across US states. Google trends provide a relative number of search popularity, not the absolute number of total searches.
#Below code creates multiple data frames for subsetting the original data set into job titles
df$job_state <- trim(df$job_state)
search <- search %>% rename(job_state=State)
data_anly<- df %>% filter((grepl("Analyst",Job.Title))) %>% group_by(job_state) %>% summarise(count=n())
d1 <- left_join(data_anly, search)
data_sci <- df %>% filter((grepl("Data Science",Job.Title)|grepl("Data Scientist",Job.Title))) %>% group_by(job_state) %>% summarise(count=n())
d2 <- left_join(data_sci, search)
data_ml <- df %>% filter((grepl("Machine Learning",Job.Title))) %>% group_by(job_state) %>% summarise(count=n())
d3 <- left_join(data_ml, search)
data_eng <- df %>% filter((grepl("Engineer",Job.Title))) %>% group_by(job_state) %>% summarise(count=n())
d4 <- left_join(data_eng, search)
#Below code creates 4 graphs for each job title, showing total number of job openings vs Google Searches per state
v1<-d1 %>% ggplot() + geom_point(aes(x=count, y=Data.Analyst,text=paste("State: ",job_state))) + geom_smooth(aes(x=count, y=Data.Analyst,text="Data Analyst"),color="red", se=FALSE)+theme_minimal()+ theme(panel.grid.major = element_blank(),panel.grid.minor = element_blank())
v2<-d2 %>% ggplot() + geom_point(aes(x=count, y=Data.Scientist,text=paste("State: ",job_state))) + geom_smooth(aes(x=count, y=Data.Scientist, text="Data Scientist"), color="blue", se=FALSE)+theme_minimal()+ theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank())
v3<-d3 %>% ggplot() + geom_point(aes(x=count, y=Machine.Learning.Engineer,text=paste("State: ",job_state))) + geom_smooth(aes(x=count, y=Machine.Learning.Engineer,text="Machine Learning Engineer"),color="orange", se=FALSE)+theme_minimal()+ theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank())
v4<-d4 %>% ggplot() + geom_point(aes(x=count, y=Data.Engineer,text=paste("State: ",job_state))) + geom_smooth(aes(x=count, y=Data.Engineer,text="Data Engineer"),color="purple", se=FALSE)+theme_minimal()+ theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank())
#Below code generates interactive plotly plots and puts together all 4 charts
fig1<- ggplotly(v1,tooltip="text")
fig2<- ggplotly(v2,tooltip="text")
fig3<- ggplotly(v3,tooltip="text")
fig4<- ggplotly(v4,tooltip="text")
fig <- subplot(fig1,fig2,fig3,fig4, nrows =2) %>% layout(title = list(text = paste0('Google Searches vs Job Openings','<br>','<sup>',' X axis: Number of job postings Y axis:Google Search trends, relative number indicating popularity, not exact number of searches')), size=I(2),titlefont = list(size = 13))
fig
NA
We mostly see a strong correlation between the interest in job titles (as per Google search numbers) and the available jobs per state. California, without any surprise, is where the highest number of job openings and biggest interest is at. Interestingly, Virginia comes second in terms of openings and interest, rather than NY.
Another important insight is that there are many cases where the interest is not met by the available jobs. Texas, Illinois and Florida have very high Google search numbers for Data Science jobs but in terms of the number of job postings, they are in the smallest range.
The last bit of insight this graph shows is the interest for different job titles. We see that Data Analyst and Data Scientist searches on Google are quite high, on a scale of 0-50, while for Machine Learning Engineer and Data Engineer, this relative spectrum is quite smaller.
Geographical Breakdown of Data Science Job Openings and Salaries
After analyzing the statewide breakdown of interest and availability of data science jobs, we thought it would be interesting to visualize on a map which cities and states have the most job openings and highest salaries.
ggmap::register_google(key = "AIzaSyB1-MjiXEIrgdT3FbflMLc8EUaQXVG3XVY")
read.csv("data_scientist.csv")
Number of Openings
states <- df %>%
group_by(job_state) %>%
count(job_state) %>%
arrange(desc(n))
cities <- df %>%
group_by(Location) %>%
count(Location) %>%
arrange(desc(n))
datatable(states, filter = 'top', colnames = c("State", "Openings"))
datatable(cities,filter = 'top', colnames = c("Cities", "Openings"))
GeoCoded <- purrr::map_df(.x = cities$Location, .f = ggmap::geocode)
geocoded_df <- dplyr::bind_cols(cities, GeoCoded) %>%
dplyr::select(
lng = lon,
lat,
dplyr::everything())
us <- readOGR(dsn = "cb_2018_us_state_500k", layer = "cb_2018_us_state_500k")
OGR data source with driver: ESRI Shapefile
Source: "/Users/dylanrosenthal/Desktop/Columbia Courses/Data Visualization/Final Project/Final Version/cb_2018_us_state_500k", layer: "cb_2018_us_state_500k"
with 56 features
It has 9 fields
Integer64 fields read as strings: ALAND AWATER
states$popuptext <- base::paste0("<b>",
"Number of Openings: ",
"</b><br />",
states$n)
openings_by_state <- merge(us, states, by.x = "STUSPS", by.y = "job_state")
geocoded_df$popuptext <- base::paste0("<b>",
"Number of Openings: ",
"</b><br />",
geocoded_df$n)
bins <- c(0, 1, 3, 6, 10, 20, 30, 60, Inf)
pal <- colorBin("RdBu", domain = states$n, bins = bins)
map_openings <- leaflet(geocoded_df) %>%
setView(lng = -98.5795, lat = 39.8283, zoom = 2.5) %>%
addProviderTiles(providers$CartoDB.PositronNoLabels) %>%
addPolygons(data = openings_by_state,
color = "white",
weight = 2,
smoothFactor = 0.5,
opacity = 1.0,
fillOpacity = 0.9,
dashArray = "2",
fillColor = ~pal(n),
highlightOptions = highlightOptions(color = "red", weight = 2,
bringToFront = TRUE),
popup = ~popuptext ,
group = "States",
label = ~htmlEscape(as.character(NAME))) %>%
addCircleMarkers(color = "orange",
label = ~htmlEscape(as.character(Location)),
fill = TRUE,
lng = ~lng,
lat = ~lat,
popup = ~popuptext,
group = "Cities") %>%
addLayersControl(
baseGroups = c("States", "Cities"),
options = layersControlOptions(collapsed = FALSE)
)
map_openings
Note: grey states have no openings
From the first map above we can see that the States with the more openings are California, Virginia, and Massachusetts. City-wide, the jobs are well distributed over the Country. However, the city with more openings are San Francisco with 69 and New York with 50. In the third place comes Washington D.C. with just 26 openings. Nonetheless, it has to be taken into consideration that the openings in the dataset are community specific. This means that there are a lot of openings in the surrounding areas of the big cities that are not, however, counted as if in their metro areas. Zooming in to San Francisco gives a clearer idea. About thirty-plus areas with openings surround the San Francisco’s metropolitan area. Santa Clara has 9 openings, Redwood City has 7, San Jose 4, Cupertino 3, and many more.
Average Salary
states_salary <- df %>%
group_by(job_state) %>%
dplyr::summarize(`Average Salary` = mean(avg_salary))
states_salary$`Average Salary` <- round(states_salary$`Average Salary`, digits = 2)
cities_salary <- df %>%
group_by(Location) %>%
dplyr::summarize(`Average Salary` = mean(avg_salary))
cities_salary$`Average Salary` <- round(cities_salary$`Average Salary`, digits = 2)
print("The median salary in the dataset is:")
[1] "The median salary in the dataset is:"
median(cities_salary$`Average Salary`)
[1] 115.865
cities_salary <- cities_salary %>%
mutate(`Median` = ifelse(`Average Salary` >= 115.2, paste("Above National Median"), paste("Below National Median")))
GeoCoded2 <- purrr::map_df(.x = cities_salary$Location, .f = ggmap::geocode)
geocoded_df2 <- dplyr::bind_cols(cities_salary, GeoCoded2) %>%
dplyr::select(
lng = lon,
lat,
dplyr::everything())
states_salary$popuptext <- base::paste0("<b>",
"Average Salary: ",
"</b><br />",
states_salary$`Average Salary`)
salary_by_state <- merge(us, states_salary, by.x = "STUSPS", by.y = "job_state")
geocoded_df2$popuptext <- base::paste0("<b>",
"City average salary: ",
"</b><br />",
geocoded_df2$`Average Salary`)
bins2 <- c(90,100,110,120,130,140,150, Inf)
my_palette <- carto.pal("green.pal", 8)
pal2 <- colorBin(my_palette, domain = states_salary$`Average Salary`, bins = bins2)
second_palette <- c("green3", "firebrick2")
pal3 = colorFactor(second_palette, domain = geocoded_df2$`Median`)
median_color = pal3(geocoded_df2$`Median`)
map_salary <- leaflet(geocoded_df2) %>%
setView(lng = -98.5795, lat = 39.8283, zoom = 2.5) %>%
addProviderTiles(providers$CartoDB.PositronNoLabels) %>%
addPolygons(data = salary_by_state,
color = "white",
weight = 2,
smoothFactor = 0.5,
opacity = 1.0,
fillOpacity = 0.9,
dashArray = "2",
fillColor = ~pal2(`Average Salary`),
highlightOptions = highlightOptions(color = "red", weight = 2,
bringToFront = TRUE),
popup = ~popuptext ,
group = "States",
label = ~htmlEscape(as.character(NAME))) %>%
addCircleMarkers(color = ~pal3(`Median`),
label = ~htmlEscape(as.character(Location)),
fill = TRUE,
lng = ~lng,
lat = ~lat,
popup = ~popuptext,
radius = ~`Average Salary`/18,
group = "Cities") %>%
addLegend(pal = pal3, values = ~geocoded_df2$Median, title = "Cities to National Median", group = "Cities") %>%
addLayersControl(
baseGroups = c("Cities", "States"),
options = layersControlOptions(collapsed = FALSE)
)
map_salary
Note: grey states have no openings
Here, we wanted to focus more on the cities. The cities that have an average salary for their openings higher than the national median are colored in green. On the other hand, cities with average salaries below the national median are colored in red. Furthermore, the size of the circles is proportional to the city’s average salary. Among the most known cities, and with a discrete number of openings, Dallas (TX) and Sacramento (CA) are the two with the highest average salary: $183,000. There are three cities with a salary of $271,000 and only one job opening. One in TX, one in CA and one in DE. Colorado Springs (CO) and Tulsa (OK) close the list with salaries of just 43k and 67k.
On the other hand, the states map shows the average salary per state. The state with the higher average is Delaware (with $271,000), but this is because it has just one opening. In the second place comes North Carolina with just slightly under $150,000. South Carolina and Montana close the list with, respectively, 95.5k and 93.75k.
Text Analysis of Data Science Job Descriptions
Following our analysis of job openings and salaries, we wanted to analyze the descriptions of the job openings to better understand what skills and abilities are desired by potential employers. We achieve this goal by breaking down specific words that commonly show up in data science job descriptions, with a special focus on words that indicate a specific skill set of the applicants.
# Add Unique ID for each row
df$id <- id(df)
# Clean up data to prepare for text analysis
df$Job.Description <- replace_url(df$Job.Description,
pattern = qdapRegex::grab("rm_url"),
replacement = "")
df$Job.Description <- str_replace_all(df$Job.Description,
pattern = "[[:digit:]]+",
replacement = "")
df$Job.Description <- str_replace_all(df$Job.Description,
pattern = "[[:punct:]]+",
replacement = "")
df$Job.Description <- trim(df$Job.Description)
df_unnest <- unnest_tokens(df, word, Job.Description)
# remove stop words
df_unnest <- anti_join(df_unnest, stop_words,
by = "word")
# only keep useful words
df_unnest <- inner_join(df_unnest, key_words,
by = "word")
(word_count <- df_unnest %>% group_by(word, word_type) %>%
summarise(count = n(), avg_salary = round((mean(avg_salary)), digits = 1))) %>%
arrange(desc(count))
# Create a wordcloud of word count for skills
skills_word_count <- filter(word_count, word_type == "skill")
wordcloud(skills_word_count$word, skills_word_count$count,
max.words = 100, colors = "pink3")

The word cloud above shows the most common skills mentioned in data science job descriptions. Some of the most common are obvious, like machine learning, modeling, knowing data languages and how to use certain applications. Some of the most common are a little more surprising, like having strong writing and communication skills, or being a good collaborator.
# Create comparison cloud for skills and tools in data science vs. data analyst jobs
comparison.cloud(analyst_sci_matrix, colors = c("salmon", "darkslategray4"), title.size= 2,
max.words = 100, title.bg.colors = "peachpuff2")

The comparison cloud above shows the most common skills and tools mentioned in data scientist versus data analyst job descriptions. The words in the data scientist job descriptions appear to be much more technical, such as machine learning, engineering, and modeling, while words in data analyst job descriptions are much more focused around the use of specific tools used for data analysis (such as Microsoft and Tableau) and communicating insights through dashboards and presentations.
# Create comparison cloud for tools in data science vs. data analyst jobs
comparison.cloud(tool_matrix, colors = c("salmon", "darkslategray4"), title.size= 2,
max.words = 100, title.bg.colors = "peachpuff2")

The comparison cloud above shows the most common tools mentioned in data scientist versus data analyst job descriptions. Once again, we can see that data scientist roles appear to be much more technical and oriented around coding for data analysis, with tools such as Python, SAS and Java topping the list, while data analysis tools (with the exception of SQL) are more focused around general business intelligence tools used for less advanced analysis, such as Excel, Tableau, and Microsoft.
Gender Pay Gap in Data Science Jobs
In this part of the project, we would like to focus on the gender pay gap among Data Scientist in the US. Unfortunately, it has been studied that male data scientists in the US earn more and have a higher earnings range than their female counterparts. Furthermore, it has been demonstrated that more than 40% of women with full-time jobs in science leave the sector or go part time after having their first child in the United States. By contrast, only 23% of new fathers leave or cut their working hours. We would like to see if these findings are also supported by the data that we have. We are going to use the 2018 Kaggle Machine Learning & Data Science Survey, the most comprehensive dataset available on the state of ML and data science.
survey <- read.csv("Gender gap in Data Science/multipleChoiceResponses.csv")
survey <- survey %>% filter(Q3 == "United States of America" | Q3 == "In which country do you currently reside?")
survey_salary <- survey %>%
filter(Q9 != "I do not wish to disclose my approximate yearly compensation")
survey_salary$min <- str_extract(survey_salary$Q9, "[^-]+")
survey_salary$max <- gsub( "(.*)-(.*)", "\\2", survey_salary$Q9)
survey_salary$max1 <- str_extract(survey_salary$max, "[^,]+")
survey_salary$min <- as.numeric(survey_salary$min)
survey_salary$max1 <- as.numeric(survey_salary$max1)
survey_salary <- survey_salary %>% mutate(average_salary = (((min + max1)/2)*1000))
survey_salary$Q1[survey_salary$Q1=="Prefer not to say"] <- "Other"
survey_salary$Q1[survey_salary$Q1=="Prefer to self-describe"] <- "Other"
survey_salary <- survey_salary%>%
filter(Q3 != "In which country do you currently reside?")
GeomSplitViolin <- ggproto(
"GeomSplitViolin",
GeomViolin,
draw_group = function(self, data, ..., draw_quantiles = NULL) {
data <- transform(data,
xminv = x - violinwidth * (x - xmin),
xmaxv = x + violinwidth * (xmax - x))
grp <- data[1,'group']
newdata <- plyr::arrange(
transform(data, x = if(grp%%2==1) xminv else xmaxv),
if(grp%%2==1) y else -y
)
newdata <- rbind(newdata[1, ], newdata, newdata[nrow(newdata), ], newdata[1, ])
newdata[c(1,nrow(newdata)-1,nrow(newdata)), 'x'] <- round(newdata[1, 'x'])
if (length(draw_quantiles) > 0 & !scales::zero_range(range(data$y))) {
stopifnot(all(draw_quantiles >= 0), all(draw_quantiles <= 1))
quantiles <- ggplot2:::create_quantile_segment_frame(data, draw_quantiles)
aesthetics <- data[rep(1, nrow(quantiles)), setdiff(names(data), c("x", "y")), drop = FALSE]
aesthetics$alpha <- rep(1, nrow(quantiles))
both <- cbind(quantiles, aesthetics)
quantile_grob <- GeomPath$draw_panel(both, ...)
ggplot2:::ggname("geom_split_violin",
grid::grobTree(GeomPolygon$draw_panel(newdata, ...), quantile_grob))
} else {
ggplot2:::ggname("geom_split_violin", GeomPolygon$draw_panel(newdata, ...))
}
}
)
geom_split_violin <- function (mapping = NULL,
data = NULL,
stat = "ydensity",
position = "identity", ...,
draw_quantiles = NULL,
trim = TRUE,
scale = "area",
na.rm = FALSE,
show.legend = NA,
inherit.aes = TRUE) {
layer(data = data,
mapping = mapping,
stat = stat,
geom = GeomSplitViolin,
position = position,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = list(trim = trim,
scale = scale,
draw_quantiles = draw_quantiles,
na.rm = na.rm, ...)
)
}
survey_salary %>%
filter(Q1 != "Other") %>%
filter(Q4 %in% c("Doctoral degree", "Master’s degree")) %>%
ggplot(aes(x = Q1, y = average_salary, fill = Q4)) +
geom_split_violin(trim=FALSE) +
theme_minimal() +
scale_y_continuous(labels = comma) +
scale_fill_manual(values = wes_palette(2, name = "GrandBudapest2")) +
labs(x = "Gender", y = "Average yearly salary", title = "Average Data Scientists' Salary by Educational Level and Gender", caption = "Source: 2018 Kaggle Machine Learning & Data Science Survey", fill = "Educational level") +
theme(plot.caption = element_text(hjust = -0.4, vjust = -2,size = 8, color ="azure4"),
axis.title.y = element_text(size = 11, vjust = 3),
axis.title.x=element_text(vjust = -0.7))

The above graph shows the average Data Scientists’ salary by educational level and gender. First of all, it can be seen that, having a Doctoral Degree instead of a Master’s Degree, allows people to earn more, independently of their sex. However, it can be seen that women with both a Doctoral and a Master’s Degree earn less than men. Also, the earnings for men are more spread out and can reach $500,000 per year, while this does not happen for women. From this graph, it can be concluded that a gender pay gap among Data Scientist in the US actually exists.
survey_salary$Q2[survey_salary$Q2=="18-21"] <- "18-24"
survey_salary$Q2[survey_salary$Q2=="22-24"] <- "18-24"
survey_salary$Q2[survey_salary$Q2=="70-79"] <- "70+"
survey_salary$Q2[survey_salary$Q2=="80+"] <- "70+"
#TAKE OTHER OUT
#ALSO NOT MANY SENIOR DATA SCIENTISTS
#CHANGE TO GEOM_BAR
survey_salary %>%
filter(Q1 != "Other") %>%
ggplot(aes(x = Q2, y = average_salary)) +
geom_bar(aes(fill = Q1), stat="identity", position = position_dodge()) +
scale_y_continuous(labels = comma) +
theme_minimal() +
labs(x = "Age", y = "Average yearly salary", title = "Average Data Scientists' Salary by Age and Gender", caption = "Source: 2018 Kaggle Machine Learning & Data Science Survey", fill = "Gender") +
scale_fill_manual(values = c("rosybrown1", "cadetblue2")) +
theme(plot.caption = element_text(hjust = -0.3, vjust = -2.3,size = 8, color ="azure4"),
axis.title.y = element_text(size = 11, vjust = 3),
axis.title.x=element_text(vjust = -0.7))

The above graph shows the average Data Scientists’ salary by age and gender. It can be seen that, at the beginning of their working career, men and women earn almost the same. However, when the age increases, at about 30 years old, the difference between the salary for men vs the one for women is evident. Thirty years is the age when a woman usually becomes a mother and, precisely in conjunction with this age group, the differences between the male and female wages are clear. Men earn almost twice as much as women. As the article by Nature suggests, “parenthood is an important driver of gender imbalance in STEM employment.” In conclusion, we hope that the gap will close in the future.
In the same repository, you can find a process book with a description of the steps taken to clean the data and create our graphs.
LS0tCnRpdGxlOiAiRGF0YSBTY2llbnRpc3RzIEpvYnMgaW4gdGhlIFVuaXRlZCBTdGF0ZXMiCmF1dGhvcnM6IER5bGFuIFJvc2VudGhhbCwgQmVuZ3VzdSBPemNhbiwgU2lsdmlhIFN1bnNlcmksIERhdmlkZSBWYWNjYXJpCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdGhlbWU6IGpvdXJuYWwKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVycm9yID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFKQoKbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHJnZGFsKQpsaWJyYXJ5KHRtYXApCmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KGdncmVwZWwpCmxpYnJhcnkoc2YpCmxpYnJhcnkoRFQpCmxpYnJhcnkoaHRtbHRvb2xzKQpsaWJyYXJ5KGxlYWZsZXQpCmxpYnJhcnkocXVhbnRlZGEpCmxpYnJhcnkobWFncml0dHIpCiNpbnN0YWxsLnBhY2thZ2VzKCJjYXJ0b2dyYXBoeSIpCmxpYnJhcnkoY2FydG9ncmFwaHkpCmxpYnJhcnkodGlkeXRleHQpCmxpYnJhcnkocGxvdHJpeCkKbGlicmFyeShkZXZ0b29scykKbGlicmFyeShnZGF0YSkKbGlicmFyeShSQ29sb3JCcmV3ZXIpCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeSh3b3JkY2xvdWQpCmxpYnJhcnkoZ2d0aGVtZXMpCmxpYnJhcnkoZ2dtYXApCmxpYnJhcnkodGV4dGRhdGEpCmxpYnJhcnkodGlkeXZlcnNlKQpyZXF1aXJlKHNjYWxlcykKbGlicmFyeSh3ZXNhbmRlcnNvbikKbGlicmFyeShwbG90bHkpCmxpYnJhcnkodG0pCiNpbnN0YWxsLnBhY2thZ2VzKCJxZGFwVG9vbHMiKQpsaWJyYXJ5KHFkYXBUb29scykKI2luc3RhbGwucGFja2FnZXMoInRleHRjbGVhbiIpCmxpYnJhcnkodGV4dGNsZWFuKQpkZiA9IHJlYWQuY3N2KCJkYXRhX3NjaWVudGlzdC5jc3YiKQpzZWFyY2ggPSByZWFkLmNzdigiR1RfRGF0YS5jc3YiKQpyID0gZ2V0T3B0aW9uKCJyZXBvcyIpCnJbIkNSQU4iXSA9ICJodHRwOi8vY3Jhbi51cy5yLXByb2plY3Qub3JnIgpvcHRpb25zKHJlcG9zID0gcikKa2V5X3dvcmRzID0gcmVhZC5jc3YoImRhdGEgc2NpZW50aXN0IGtleSB3b3Jkcy5jc3YiKQpgYGAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmRmIDwtIHNlbGVjdChkZiwgLShYKSkKYGBgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpwYWwxIDwtIHdlc19wYWxldHRlKDI0LCBuYW1lID0gIlppc3NvdTEiLCB0eXBlID0gImNvbnRpbnVvdXMiKQoKZGYkSm9iLlRpdGxlIDwtIHN0cl9leHRyYWN0KGRmJEpvYi5UaXRsZSwgIlteLV0rIikgCmRmJEpvYi5UaXRsZSA8LSBzdHJfZXh0cmFjdChkZiRKb2IuVGl0bGUsICJbXi9dKyIpIApkZiRKb2IuVGl0bGUgPC0gc3RyX2V4dHJhY3QoZGYkSm9iLlRpdGxlLCAiW14sXSsiKSAKZGYgPC0gZmlsdGVyKGRmLCBKb2IuVGl0bGUgIT0gIkUiKQpkZiA8LSBmaWx0ZXIoZGYsIEpvYi5UaXRsZSAhPSAiQUkiKQpkZiA8LSBmaWx0ZXIoZGYsIEpvYi5UaXRsZSAhPSAiSW4iKQpkZiA8LSBmaWx0ZXIoZGYsIEpvYi5UaXRsZSAhPSAiVlAiKQpkZiA8LSBmaWx0ZXIoZGYsIEpvYi5UaXRsZSAhPSAiSHlkcm9nZW4iKQpkZiRKb2IuVGl0bGUgPC0gZ3N1YigiU2VuaW9yIiwgIiIsIGRmJEpvYi5UaXRsZSkKZGYkSm9iLlRpdGxlIDwtIGdzdWIoIlNyLiIsICIiLCBkZiRKb2IuVGl0bGUpCmRmJEpvYi5UaXRsZSA8LSBnc3ViKCJTdGFmZiIsICIiLCBkZiRKb2IuVGl0bGUpCmRmJEpvYi5UaXRsZVtncmVwbCgnQW5hbHlzdCcsZGYkSm9iLlRpdGxlKV0gPC0gJ0RhdGEgQW5hbHlzdCcKZGYkSm9iLlRpdGxlW2dyZXBsKCdTY2llbnRpc3QnLGRmJEpvYi5UaXRsZSldIDwtICdEYXRhIFNjaWVudGlzdCcKZGYkSm9iLlRpdGxlW2dyZXBsKCdTQ0lFTlRJU1QnLGRmJEpvYi5UaXRsZSldIDwtICdEYXRhIFNjaWVudGlzdCcKZGYkSm9iLlRpdGxlW2dyZXBsKCdEYXRhIFNjaWVuY2UnLGRmJEpvYi5UaXRsZSldIDwtICdEYXRhIFNjaWVudGlzdCcKZGYkSm9iLlRpdGxlW2dyZXBsKCdEYXRhIEVuZ2luZWVyJyxkZiRKb2IuVGl0bGUpXSA8LSAnRGF0YSBFbmdpbmVlcicKZGYkSm9iLlRpdGxlW2dyZXBsKCdFTkdJTkVFUicsZGYkSm9iLlRpdGxlKV0gPC0gJ0RhdGEgRW5naW5lZXInCmRmJEpvYi5UaXRsZVtncmVwbCgnTW9kZWxlcicsZGYkSm9iLlRpdGxlKV0gPC0gJ0RhdGEgTW9kZWxlcicKZGYkSm9iLlRpdGxlW2dyZXBsKCdBbmFseXRpY3MnLGRmJEpvYi5UaXRsZSldIDwtICdBbmFseXRpY3MnCmRmJEpvYi5UaXRsZVtncmVwbCgnTWFuYWdlciAnLGRmJEpvYi5UaXRsZSldIDwtICdNYW5hZ2VyJwpkZiRKb2IuVGl0bGVbZ3JlcGwoJ01MJyxkZiRKb2IuVGl0bGUpXSA8LSAnTWFjaGluZSBMZWFybmluZyBFbmdpbmVlcicKCmRmICU+JQpzZWxlY3QoSm9iLlRpdGxlLCBtYXhfc2FsYXJ5KSAlPiUKZ3JvdXBfYnkoSm9iLlRpdGxlKSAlPiUKc3VtbWFyaXNlKG1lYW4gPSBtZWFuKG1heF9zYWxhcnkpKSAlPiUKZ2dwbG90KGFlcyh4ID0gcmVvcmRlcihKb2IuVGl0bGUsIG1lYW4pLCB5ID0gbWVhbiwgZmlsbCA9IEpvYi5UaXRsZSkpICsKZ2VvbV9jb2woKSArCmNvb3JkX2ZsaXAoKSArCmxhYnMoeCA9ICJKb2IgVGl0bGUiLCB5ID0gIkF2ZXJhZ2UgU2FsYXJ5IChpbiB0aG91c2FuZHMgb2YgJCkiLCBjYXB0aW9uID0gIlNvdXJjZTogMjAxOCBLYWdnbGUgRGF0YSBTY2llbmNlIEpvYiBQb3N0aW5nIG9uIEdsYXNzZG9yIikgKwpnZ3RpdGxlKCJBdmVyYWdlIHNhbGFyeSBieSBKb2IiKSArCnNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1wYWwxKSArCnRoZW1lX21pbmltYWwoKSArCnRoZW1lKGxlZ2VuZC50aXRsZT1lbGVtZW50X2JsYW5rKCksCiAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgICAgcGxvdC5jYXB0aW9uID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gLTEuMywgdmp1c3QgPSAtMi4zLHNpemUgPSA4LCBjb2xvciA9ImF6dXJlNCIpLAogICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gLTAuNCwgdmp1c3Q9IDIuNSksCiAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dCh2anVzdCA9IC0wLjUpLAogICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQodmp1c3QgPSAzKSkKYGBgCgpUaGUgYWltIG9mIG91ciBwcm9qZWN0IGlzIHRvIGludmVzdGlnYXRlIERhdGEgU2NpZW5jZSBKb2JzIGluIHRoZSBVUy4gV2UgYXJlIGludGVyZXN0ZWQgaW4gdGhlIHRvcGljIHNpbmNlIHdlIGFyZSBzdHVkeWluZyB0byBiZWNvbWUgRGF0YSBTY2llbnRpc3RzLCBhbmQgdGhlcmVmb3JlLCB3b3VsZCBsaWtlIHRvIGtub3cgbW9yZSBhYm91dCBpdC4gV2UgYXJlIGdvaW5nIHRvIHVzZSB0aGUgZGF0YSBhYm91dCBEYXRhIFNjaWVuY2UgSm9iIFBvc3Rpbmcgb24gR2xhc3Nkb29yLiBJdCB3YXMgY29sbGVjdGVkIGJ5IHdlYi1zY3JhcGluZyBqb2IgcG9zdHMgZnJvbSBHbGFzc2Rvb3IgZm9yIGRhdGEgc2NpZW5jZSBqb2JzLgoKV2UgaGF2ZSBkZWNpZGVkIHRvIHN0YXJ0IG91ciBhbmFseXNpcyB3aXRoIGEgZ3JhcGggdGhhdCBjb3VsZCBnaXZlIGEgc29ydCBvZiBnZW5lcmFsIG92ZXJ2aWV3IG9mIERhdGEgU2NpZW5jZSBKb2JzIChhbmQgcmVsYXRlZCkgYW5kIHNhbGFyaWVzLiBGcm9tIHRoZSBncmFwaCBhYm92ZSwgaXQgY2FuIGJlIHNlZW4gdGhhdCBiZWluZyBhIE1hbmFnZXIgaXMgZGVmaW5pdGVseSB0aGUgcG9zaXRpb24gdGhhdCwgb24gYXZlcmFnZSwgcGF5cyB0aGUgbW9zdC4gRGF0YSBBcmNoaXRlY3QgaW1tZWRpYXRlbHkgZm9sbG93cyB3aXRoIGFuIGF2ZXJhZ2UgeWVhcmx5IHNhbGFyeSBvZiAkMjAwLDAwMC4gQ29udHJhcnkgdG8gb3VyIGV4cGVjdGF0aW9uLCBEYXRhIE1vZGVsZXIgZG9lcyBub3QgZWFybiB0aGF0IG11Y2ggYW5kIGNhbiBiZSBmb3VuZCBhdCBhbG1vc3QgdGhlIGVuZCBvZiB0aGUgcmFua2luZy4KCiMjIyBEYXRhIFNjaWVuY2UgU2FsYXJpZXMgYnkgU2VjdG9yCgpJbiB0aGlzIHBhcnQgb2YgdGhlIHByb2plY3QsIHdlIGFyZSBnb2luZyB0byBhbmFseXplIHdoaWNoIHNlY3RvcnMgaGF2ZSB0aGUgaGlnaGVzdCBzYWxhcmllcy4gV2UgdGhpbmsgdGhpcyBpcyBhIHVzZWZ1bCBiZWNhdXNlIGl0IGhpZ2hsaWdodHMgdGhlIHRyZW5kIHRoYXQsIGV2ZW4gaW4gdGhlIGZpZWxkIG9mIGRhdGEgc2NpZW5jZSwgdGhlcmUgY2FuIGJlIHNpZ25pZmljYW50IGRpZmZlcmVuY2VzIGluIHNhbGFyeSBkZXBlbmRpbmcgb24gdGhlIGZpZWxkIHRoYXQgYW4gaW5kaXZpZHVhbCBtYXkgY2hvb3NlIHRvIGdvIGludG8uCgpgYGB7ciwgZXZhbD1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBpbnN0YWxsLnBhY2thZ2VzKCJ3ZXNhbmRlcnNvbiIpCmxpYnJhcnkoZ2d0aGVtZXMpCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KHdlc2FuZGVyc29uKQpsaWJyYXJ5KGdncmVwZWwpCnBhbCA8LSB3ZXNfcGFsZXR0ZSgyMiwgbmFtZSA9ICJGYW50YXN0aWNGb3gxIiwgdHlwZSA9ICJjb250aW51b3VzIikKYGBgCgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcGFsIDwtIHdlc19wYWxldHRlKDIyLCBuYW1lID0gIkZhbnRhc3RpY0ZveDEiLCB0eXBlID0gImNvbnRpbnVvdXMiKQpkMSA8LSBkZiAlPiUgZmlsdGVyKCFTZWN0b3I9PSItMSIpICU+JSBncm91cF9ieShTZWN0b3IpICU+JSBzdW1tYXJpc2UoSm9iX09wZW5pbmdzPW4oKSwgQXZlcmFnZV9TYWxhcnk9bWVhbihhdmdfc2FsYXJ5KSkgJT4lIGZpbHRlcihKb2JfT3BlbmluZ3MgPjMpICU+JSBnZ3Bsb3QoKSArIApnZW9tX2NvbChhZXMoeCA9IHJlb3JkZXIoU2VjdG9yLCBBdmVyYWdlX1NhbGFyeSksIHkgPSByb3VuZChBdmVyYWdlX1NhbGFyeSksIGZpbGw9U2VjdG9yLCB0ZXh0PXBhc3RlMCgiQXZlcmFnZSBTYWxhcnk6IiAscm91bmQoQXZlcmFnZV9TYWxhcnkpLCIgMDAwcyBVU0QiKSksIHNpemUgPSAxLCBjb2xvciA9ICJ3aGl0ZSIpICsgCiAgZ2VvbV9saW5lKGFlcyh4ID0gcmVvcmRlcihTZWN0b3IsQXZlcmFnZV9TYWxhcnkpLCB5ID0gSm9iX09wZW5pbmdzKSwgc2l6ZSA9IDAuNSwgY29sb3I9ImdyYXkzNSIsIGdyb3VwID0gMSkrCiAgZ2VvbV9wb2ludChhZXMoeCA9IHJlb3JkZXIoU2VjdG9yLEF2ZXJhZ2VfU2FsYXJ5KSwgeSA9IEpvYl9PcGVuaW5ncywgdGV4dD1wYXN0ZSgiTnVtYmVyIG9mIEpvYiBQb3N0aW5nczogIixKb2JfT3BlbmluZ3MpKSkrbGFicyh4PSJTZWN0b3JzIiwgeT0iQXZlcmFnZSBBbm51YWwgU2FsYXJ5IChpbiB0aG91c2FuZHMgb2YgJCkiKSt0aGVtZV90dWZ0ZSgpK2d1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAwLjEpKSkgK3RoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIikgK3RoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gNSksIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSA1KSkrICAKdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSkrCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIsIGhqdXN0ID0gMC41KSkrIAp0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkrCnNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1wYWwpCgojQmVsb3cgdHVybnMgZDEgdmlzdWFsaXphdGlvbiBpbnRvIGludGVyYWN0aXZlIHBsb3RseSBncmFwaApnZ3Bsb3RseShkMSwgdG9vbHRpcD0idGV4dCIpICU+JQogIGxheW91dCh0aXRsZSA9IGxpc3QodGV4dCA9IHBhc3RlMCgnRGF0YSBSZWxhdGVkIEpvYiBQb3N0aW5nIFNhbGFyaWVzIHBlciBTZWN0b3InLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnPGJyPicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICc8c3VwPicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdDb3ZlcnMgam9iIHRpdGxlcyBzdWNoIGFzIGRhdGEgc2NpZW50aXN0LCBkYXRhIGFuYWx5c3QsIGRhdGEgZW5naW5lZXIuLi4nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnPGJyPicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICc8c3VwPicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdIb3ZlciBvdmVyIHRoZSBibGFjayBsaW5lIHRvIHNlZSBudW1iZXIgb2Ygam9iIG9wZW5pbmdzIHBlciBzZWN0b3IuJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzwvc3VwPicpKSwgc2l6ZT1JKDMpLHRpdGxlZm9udCA9IGxpc3Qoc2l6ZSA9IDEzKSkKCmBgYAoKVGhlIGFib3ZlIGdyYXBoIHNob3dzIHRoZSBhdmVyYWdlIHNhbGFyeSBmb3IgZGF0YS1yZWxhdGVkIGpvYiBwb3N0aW5ncyBhY3Jvc3MgZGlmZmVyZW50IHNlY3RvcnMuIEhpZ2hlc3QgcGF5aW5nIHNlY3RvcnMgYXJlIE1lZGlhIGFuZCBSZXRhaWwuIEhvd2V2ZXIsIHRoZSB0b3RhbCBudW1iZXIgb2Ygb3BlbmluZ3MgZnJvbSB0aG9zZSBzZWN0b3JzIGluIHRoZSBkYXRhIHNldCBhcmUgb25seSA1IGFuZCA3IHJlc3BlY3RpdmVseS4gVGhlIHNlY3RvciB3aXRoIHRoZSBoaWdoZXN0IG9wcG9ydHVuaXR5IGZvciBkYXRhIHJlbGF0ZWQgam9icyBzZWVtIHRvIGJlIEJ1c2luZXNzIFNlcnZpY2VzIGFuZCBJbmZvcm1hdGlvbiBUZWNobm9sb2d5LCBiZWNhdXNlLCBkZXNwaXRlIGJlaW5nIG9uIHRoZSBhdmVyYWdlIHNhbGFyeSByYW5nZSBhY3Jvc3Mgc2VjdG9ycywgdGhleSBoYXZlIHRoZSBoaWdoZXN0IG51bWJlciBvZiBvcGVuaW5ncyBhbmQgdGhlIGJpZ2dlc3Qgam9iIG9wcG9ydHVuaXR5LgoKIyMjIEludGVyZXN0IGluIERhdGEgU2NpZW5jZSBKb2JzIGFuZCBBdmFpbGFibGUgSm9icyBwZXIgU3RhdGUKCkFmdGVyIGV4cGxvcmluZyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gc2VjdG9ycyBhbmQgc2FsYXJpZXMsIHdlIG5leHQgdHJ5IHRvIGFzc2VzcyB0aGUgcG9wdWxhcml0eSBvZiBkYXRhIHNjaWVuY2Ugcm9sZXMsIGFuZCBob3cgaXQgZGlmZmVyZW50aWF0ZXMgYnkgc3RhdGUuIFdlIGFyZSBhc3N1bWluZyB0aGF0IEdvb2dsZSBzZWFyY2ggdHJlbmRzIGZvciBkYXRhIHNjaWVuY2UgcmVsYXRlZCBzZWFyY2ggdGVybXMgKHN1Y2ggYXMgZGF0YSBtb2RlbGVyLCBkYXRhIGFyY2hpdGVjdCBhbmQgZGF0YSBlbmdpbmVlcikgYXJlIGFuIGVmZmVjdGl2ZSBwcm94eSBmb3IgaW50ZXJlc3QuIFdlIHRoZW4gY29tcGFyZSBob3cgaW50ZXJlc3QgaW4gZGF0YSBzY2llbmNlIHJvbGVzLCBhcyBtZWFzdXJlZCBieSBHb29nbGUgdHJlbmRzLCBpcyByZWxhdGVkIHRvIHRoZSBhY3R1YWwgbnVtYmVyIG9mIGpvYiBvcGVuaW5ncy4gRm9yIHRoaXMgcHVycG9zZSwgd2UgcHVsbGVkIEdvb2dsZSBTZWFyY2ggdHJlbmRzIGZvciB0aGUgZm9sbG93aW5nIGpvYiB0aXRsZXMgYWNyb3NzIFVTIHN0YXRlcy4gR29vZ2xlIHRyZW5kcyBwcm92aWRlIGEgcmVsYXRpdmUgbnVtYmVyIG9mIHNlYXJjaCBwb3B1bGFyaXR5LCBub3QgdGhlIGFic29sdXRlIG51bWJlciBvZiB0b3RhbCBzZWFyY2hlcy4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiNCZWxvdyBjb2RlIGNyZWF0ZXMgbXVsdGlwbGUgZGF0YSBmcmFtZXMgZm9yIHN1YnNldHRpbmcgdGhlIG9yaWdpbmFsIGRhdGEgc2V0IGludG8gam9iIHRpdGxlcwoKZGYkam9iX3N0YXRlIDwtIHRyaW0oZGYkam9iX3N0YXRlKQoKc2VhcmNoIDwtIHNlYXJjaCAlPiUgcmVuYW1lKGpvYl9zdGF0ZT1TdGF0ZSkKCmRhdGFfYW5seTwtIGRmICU+JSBmaWx0ZXIoKGdyZXBsKCJBbmFseXN0IixKb2IuVGl0bGUpKSkgJT4lICBncm91cF9ieShqb2Jfc3RhdGUpICU+JSBzdW1tYXJpc2UoY291bnQ9bigpKQpkMSA8LSBsZWZ0X2pvaW4oZGF0YV9hbmx5LCBzZWFyY2gpCgpkYXRhX3NjaSA8LSBkZiAlPiUgZmlsdGVyKChncmVwbCgiRGF0YSBTY2llbmNlIixKb2IuVGl0bGUpfGdyZXBsKCJEYXRhIFNjaWVudGlzdCIsSm9iLlRpdGxlKSkpICU+JSAgZ3JvdXBfYnkoam9iX3N0YXRlKSAlPiUgc3VtbWFyaXNlKGNvdW50PW4oKSkKZDIgPC0gbGVmdF9qb2luKGRhdGFfc2NpLCBzZWFyY2gpCgpkYXRhX21sIDwtIGRmICU+JSBmaWx0ZXIoKGdyZXBsKCJNYWNoaW5lIExlYXJuaW5nIixKb2IuVGl0bGUpKSkgJT4lICBncm91cF9ieShqb2Jfc3RhdGUpICU+JSBzdW1tYXJpc2UoY291bnQ9bigpKQpkMyA8LSBsZWZ0X2pvaW4oZGF0YV9tbCwgc2VhcmNoKQoKZGF0YV9lbmcgPC0gZGYgJT4lIGZpbHRlcigoZ3JlcGwoIkVuZ2luZWVyIixKb2IuVGl0bGUpKSkgJT4lICBncm91cF9ieShqb2Jfc3RhdGUpICU+JSBzdW1tYXJpc2UoY291bnQ9bigpKQpkNCA8LSBsZWZ0X2pvaW4oZGF0YV9lbmcsIHNlYXJjaCkKCgojQmVsb3cgY29kZSBjcmVhdGVzIDQgZ3JhcGhzIGZvciBlYWNoIGpvYiB0aXRsZSwgc2hvd2luZyB0b3RhbCBudW1iZXIgb2Ygam9iIG9wZW5pbmdzIHZzIEdvb2dsZSBTZWFyY2hlcyBwZXIgc3RhdGUKdjE8LWQxICU+JSBnZ3Bsb3QoKSArIGdlb21fcG9pbnQoYWVzKHg9Y291bnQsIHk9RGF0YS5BbmFseXN0LHRleHQ9cGFzdGUoIlN0YXRlOiAiLGpvYl9zdGF0ZSkpKSArIGdlb21fc21vb3RoKGFlcyh4PWNvdW50LCB5PURhdGEuQW5hbHlzdCx0ZXh0PSJEYXRhIEFuYWx5c3QiKSxjb2xvcj0icmVkIiwgc2U9RkFMU0UpK3RoZW1lX21pbmltYWwoKSsgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSxwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpKQoKdjI8LWQyICU+JSBnZ3Bsb3QoKSArIGdlb21fcG9pbnQoYWVzKHg9Y291bnQsIHk9RGF0YS5TY2llbnRpc3QsdGV4dD1wYXN0ZSgiU3RhdGU6ICIsam9iX3N0YXRlKSkpICsgZ2VvbV9zbW9vdGgoYWVzKHg9Y291bnQsIHk9RGF0YS5TY2llbnRpc3QsIHRleHQ9IkRhdGEgU2NpZW50aXN0IiksIGNvbG9yPSJibHVlIiwgc2U9RkFMU0UpK3RoZW1lX21pbmltYWwoKSsgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSkKCnYzPC1kMyAlPiUgZ2dwbG90KCkgKyBnZW9tX3BvaW50KGFlcyh4PWNvdW50LCB5PU1hY2hpbmUuTGVhcm5pbmcuRW5naW5lZXIsdGV4dD1wYXN0ZSgiU3RhdGU6ICIsam9iX3N0YXRlKSkpICsgZ2VvbV9zbW9vdGgoYWVzKHg9Y291bnQsIHk9TWFjaGluZS5MZWFybmluZy5FbmdpbmVlcix0ZXh0PSJNYWNoaW5lIExlYXJuaW5nIEVuZ2luZWVyIiksY29sb3I9Im9yYW5nZSIsIHNlPUZBTFNFKSt0aGVtZV9taW5pbWFsKCkrIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCkpCgp2NDwtZDQgJT4lIGdncGxvdCgpICsgZ2VvbV9wb2ludChhZXMoeD1jb3VudCwgeT1EYXRhLkVuZ2luZWVyLHRleHQ9cGFzdGUoIlN0YXRlOiAiLGpvYl9zdGF0ZSkpKSArIGdlb21fc21vb3RoKGFlcyh4PWNvdW50LCB5PURhdGEuRW5naW5lZXIsdGV4dD0iRGF0YSBFbmdpbmVlciIpLGNvbG9yPSJwdXJwbGUiLCBzZT1GQUxTRSkrdGhlbWVfbWluaW1hbCgpKyB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpKQoKYGBgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojQmVsb3cgY29kZSBnZW5lcmF0ZXMgaW50ZXJhY3RpdmUgcGxvdGx5IHBsb3RzIGFuZCBwdXRzIHRvZ2V0aGVyIGFsbCA0IGNoYXJ0cwoKZmlnMTwtIGdncGxvdGx5KHYxLHRvb2x0aXA9InRleHQiKQpmaWcyPC0gZ2dwbG90bHkodjIsdG9vbHRpcD0idGV4dCIpCmZpZzM8LSBnZ3Bsb3RseSh2Myx0b29sdGlwPSJ0ZXh0IikKZmlnNDwtIGdncGxvdGx5KHY0LHRvb2x0aXA9InRleHQiKQoKZmlnIDwtIHN1YnBsb3QoZmlnMSxmaWcyLGZpZzMsZmlnNCwgbnJvd3MgPTIpICU+JSBsYXlvdXQodGl0bGUgPSBsaXN0KHRleHQgPSBwYXN0ZTAoJ0dvb2dsZSBTZWFyY2hlcyB2cyBKb2IgT3BlbmluZ3MnLCc8YnI+JywnPHN1cD4nLCcgWCBheGlzOiBOdW1iZXIgb2Ygam9iIHBvc3RpbmdzICAgIFkgYXhpczpHb29nbGUgU2VhcmNoIHRyZW5kcywgcmVsYXRpdmUgbnVtYmVyIGluZGljYXRpbmcgcG9wdWxhcml0eSwgbm90IGV4YWN0IG51bWJlciBvZiBzZWFyY2hlcycpKSwgc2l6ZT1JKDIpLHRpdGxlZm9udCA9IGxpc3Qoc2l6ZSA9IDEzKSkKCmZpZwoKYGBgCgpXZSBtb3N0bHkgc2VlIGEgc3Ryb25nIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIGludGVyZXN0IGluIGpvYiB0aXRsZXMgKGFzIHBlciBHb29nbGUgc2VhcmNoIG51bWJlcnMpIGFuZCB0aGUgYXZhaWxhYmxlIGpvYnMgcGVyIHN0YXRlLiBDYWxpZm9ybmlhLCB3aXRob3V0IGFueSBzdXJwcmlzZSwgaXMgd2hlcmUgdGhlIGhpZ2hlc3QgbnVtYmVyIG9mIGpvYiBvcGVuaW5ncyBhbmQgYmlnZ2VzdCBpbnRlcmVzdCBpcyBhdC4gSW50ZXJlc3RpbmdseSwgVmlyZ2luaWEgY29tZXMgc2Vjb25kIGluIHRlcm1zIG9mIG9wZW5pbmdzIGFuZCBpbnRlcmVzdCwgcmF0aGVyIHRoYW4gTlkuCgpBbm90aGVyIGltcG9ydGFudCBpbnNpZ2h0IGlzIHRoYXQgdGhlcmUgYXJlIG1hbnkgY2FzZXMgd2hlcmUgdGhlIGludGVyZXN0IGlzIG5vdCBtZXQgYnkgdGhlIGF2YWlsYWJsZSBqb2JzLiBUZXhhcywgSWxsaW5vaXMgYW5kIEZsb3JpZGEgaGF2ZSB2ZXJ5IGhpZ2ggR29vZ2xlIHNlYXJjaCBudW1iZXJzIGZvciBEYXRhIFNjaWVuY2Ugam9icyBidXQgaW4gdGVybXMgb2YgdGhlIG51bWJlciBvZiBqb2IgcG9zdGluZ3MsIHRoZXkgYXJlIGluIHRoZSBzbWFsbGVzdCByYW5nZS4KClRoZSBsYXN0IGJpdCBvZiBpbnNpZ2h0IHRoaXMgZ3JhcGggc2hvd3MgaXMgdGhlIGludGVyZXN0IGZvciBkaWZmZXJlbnQgam9iIHRpdGxlcy4gV2Ugc2VlIHRoYXQgRGF0YSBBbmFseXN0IGFuZCBEYXRhIFNjaWVudGlzdCBzZWFyY2hlcyBvbiBHb29nbGUgYXJlIHF1aXRlIGhpZ2gsIG9uIGEgc2NhbGUgb2YgMC01MCwgd2hpbGUgZm9yIE1hY2hpbmUgTGVhcm5pbmcgRW5naW5lZXIgYW5kIERhdGEgRW5naW5lZXIsIHRoaXMgcmVsYXRpdmUgc3BlY3RydW0gaXMgcXVpdGUgc21hbGxlci4gCgojIyMgR2VvZ3JhcGhpY2FsIEJyZWFrZG93biBvZiBEYXRhIFNjaWVuY2UgSm9iIE9wZW5pbmdzIGFuZCBTYWxhcmllcwoKQWZ0ZXIgYW5hbHl6aW5nIHRoZSBzdGF0ZXdpZGUgYnJlYWtkb3duIG9mIGludGVyZXN0IGFuZCBhdmFpbGFiaWxpdHkgb2YgZGF0YSBzY2llbmNlIGpvYnMsIHdlIHRob3VnaHQgaXQgd291bGQgYmUgaW50ZXJlc3RpbmcgdG8gdmlzdWFsaXplIG9uIGEgbWFwIHdoaWNoIGNpdGllcyBhbmQgc3RhdGVzIGhhdmUgdGhlIG1vc3Qgam9iIG9wZW5pbmdzIGFuZCBoaWdoZXN0IHNhbGFyaWVzLiAgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpnZ21hcDo6cmVnaXN0ZXJfZ29vZ2xlKGtleSA9ICJBSXphU3lCMS1NamlYRUlyZ2RUM0ZiZmxNTGM4RVVhUVhWRzNYVlkiKQpgYGAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnJlYWQuY3N2KCJkYXRhX3NjaWVudGlzdC5jc3YiKQpgYGAKCiMjIyMgTnVtYmVyIG9mIE9wZW5pbmdzCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpzdGF0ZXMgPC0gZGYgJT4lCiAgZ3JvdXBfYnkoam9iX3N0YXRlKSAlPiUKICBjb3VudChqb2Jfc3RhdGUpICU+JQogIGFycmFuZ2UoZGVzYyhuKSkKCmNpdGllcyA8LSBkZiAlPiUKICBncm91cF9ieShMb2NhdGlvbikgJT4lCiAgY291bnQoTG9jYXRpb24pICU+JQogIGFycmFuZ2UoZGVzYyhuKSkKYGBgCgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZGF0YXRhYmxlKHN0YXRlcywgZmlsdGVyID0gJ3RvcCcsIGNvbG5hbWVzID0gYygiU3RhdGUiLCAiT3BlbmluZ3MiKSkgCmRhdGF0YWJsZShjaXRpZXMsZmlsdGVyID0gJ3RvcCcsIGNvbG5hbWVzID0gYygiQ2l0aWVzIiwgIk9wZW5pbmdzIikpIApgYGAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQpHZW9Db2RlZCA8LSBwdXJycjo6bWFwX2RmKC54ID0gY2l0aWVzJExvY2F0aW9uLCAuZiA9IGdnbWFwOjpnZW9jb2RlKQpgYGAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cmdlb2NvZGVkX2RmIDwtIGRwbHlyOjpiaW5kX2NvbHMoY2l0aWVzLCBHZW9Db2RlZCkgJT4lIAogIGRwbHlyOjpzZWxlY3QoCiAgICBsbmcgPSBsb24sCiAgICBsYXQsCiAgICBkcGx5cjo6ZXZlcnl0aGluZygpKQpgYGAKCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQp1cyA8LSByZWFkT0dSKGRzbiA9ICJjYl8yMDE4X3VzX3N0YXRlXzUwMGsiLCBsYXllciA9ICJjYl8yMDE4X3VzX3N0YXRlXzUwMGsiKQpgYGAKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kc3RhdGVzJHBvcHVwdGV4dCA8LSBiYXNlOjpwYXN0ZTAoIjxiPiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTnVtYmVyIG9mIE9wZW5pbmdzOiAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiPC9iPjxiciAvPiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0YXRlcyRuKQpvcGVuaW5nc19ieV9zdGF0ZSA8LSBtZXJnZSh1cywgc3RhdGVzLCBieS54ID0gIlNUVVNQUyIsIGJ5LnkgPSAiam9iX3N0YXRlIikKYGBgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpnZW9jb2RlZF9kZiRwb3B1cHRleHQgPC0gYmFzZTo6cGFzdGUwKCI8Yj4iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk51bWJlciBvZiBPcGVuaW5nczogIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjwvYj48YnIgLz4iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW9jb2RlZF9kZiRuKQpgYGAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmJpbnMgPC0gYygwLCAxLCAzLCA2LCAxMCwgMjAsIDMwLCA2MCwgSW5mKQpwYWwgPC0gY29sb3JCaW4oIlJkQnUiLCBkb21haW4gPSBzdGF0ZXMkbiwgYmlucyA9IGJpbnMpCmBgYAoKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm1hcF9vcGVuaW5ncyA8LSBsZWFmbGV0KGdlb2NvZGVkX2RmKSAlPiUKICBzZXRWaWV3KGxuZyA9IC05OC41Nzk1LCBsYXQgPSAzOS44MjgzLCB6b29tID0gMi41KSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uTm9MYWJlbHMpICU+JQogIGFkZFBvbHlnb25zKGRhdGEgPSBvcGVuaW5nc19ieV9zdGF0ZSwgCiAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwgCiAgICAgICAgICAgIHdlaWdodCA9IDIsIAogICAgICAgICAgICBzbW9vdGhGYWN0b3IgPSAwLjUsCiAgICAgICAgICAgIG9wYWNpdHkgPSAxLjAsIAogICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDAuOSwKICAgICAgICAgICAgZGFzaEFycmF5ID0gIjIiLAogICAgICAgICAgICBmaWxsQ29sb3IgPSB+cGFsKG4pLAogICAgICAgICAgICBoaWdobGlnaHRPcHRpb25zID0gaGlnaGxpZ2h0T3B0aW9ucyhjb2xvciA9ICJyZWQiLCB3ZWlnaHQgPSAyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmluZ1RvRnJvbnQgPSBUUlVFKSwKICAgICAgICAgICAgcG9wdXAgPSB+cG9wdXB0ZXh0ICwKICAgICAgICAgICAgZ3JvdXAgPSAiU3RhdGVzIiwKICAgICAgICAgICAgbGFiZWwgPSB+aHRtbEVzY2FwZShhcy5jaGFyYWN0ZXIoTkFNRSkpKSAlPiUKICBhZGRDaXJjbGVNYXJrZXJzKGNvbG9yID0gIm9yYW5nZSIsCiAgICAgICAgICAgICBsYWJlbCA9IH5odG1sRXNjYXBlKGFzLmNoYXJhY3RlcihMb2NhdGlvbikpLAogICAgICAgICAgICAgZmlsbCA9IFRSVUUsCiAgICAgICAgICAgICBsbmcgPSB+bG5nLCAKICAgICAgICAgICAgIGxhdCA9IH5sYXQsCiAgICAgICAgICAgICBwb3B1cCA9IH5wb3B1cHRleHQsCiAgICAgICAgICAgICBncm91cCA9ICJDaXRpZXMiKSAlPiUKICBhZGRMYXllcnNDb250cm9sKAogICAgIGJhc2VHcm91cHMgPSBjKCJTdGF0ZXMiLCAiQ2l0aWVzIiksCiAgICAgb3B0aW9ucyA9IGxheWVyc0NvbnRyb2xPcHRpb25zKGNvbGxhcHNlZCA9IEZBTFNFKQogICkKCm1hcF9vcGVuaW5ncwpgYGAKCioqTm90ZTogZ3JleSBzdGF0ZXMgaGF2ZSBubyBvcGVuaW5ncyoqCgpGcm9tIHRoZSBmaXJzdCBtYXAgYWJvdmUgd2UgY2FuIHNlZSB0aGF0IHRoZSBTdGF0ZXMgd2l0aCB0aGUgbW9yZSBvcGVuaW5ncyBhcmUgQ2FsaWZvcm5pYSwgVmlyZ2luaWEsIGFuZCBNYXNzYWNodXNldHRzLiBDaXR5LXdpZGUsIHRoZSBqb2JzIGFyZSB3ZWxsIGRpc3RyaWJ1dGVkIG92ZXIgdGhlIENvdW50cnkuIEhvd2V2ZXIsIHRoZSBjaXR5IHdpdGggbW9yZSBvcGVuaW5ncyBhcmUgU2FuIEZyYW5jaXNjbyB3aXRoIDY5IGFuZCBOZXcgWW9yayB3aXRoIDUwLiBJbiB0aGUgdGhpcmQgcGxhY2UgY29tZXMgV2FzaGluZ3RvbiBELkMuIHdpdGgganVzdCAyNiBvcGVuaW5ncy4gTm9uZXRoZWxlc3MsIGl0IGhhcyB0byBiZSB0YWtlbiBpbnRvIGNvbnNpZGVyYXRpb24gdGhhdCB0aGUgb3BlbmluZ3MgaW4gdGhlIGRhdGFzZXQgYXJlIGNvbW11bml0eSBzcGVjaWZpYy4gVGhpcyBtZWFucyB0aGF0IHRoZXJlIGFyZSBhIGxvdCBvZiBvcGVuaW5ncyBpbiB0aGUgc3Vycm91bmRpbmcgYXJlYXMgb2YgdGhlIGJpZyBjaXRpZXMgdGhhdCBhcmUgbm90LCBob3dldmVyLCBjb3VudGVkIGFzIGlmIGluIHRoZWlyIG1ldHJvIGFyZWFzLiBab29taW5nIGluIHRvIFNhbiBGcmFuY2lzY28gZ2l2ZXMgYSBjbGVhcmVyIGlkZWEuIEFib3V0IHRoaXJ0eS1wbHVzIGFyZWFzIHdpdGggb3BlbmluZ3Mgc3Vycm91bmQgdGhlIFNhbiBGcmFuY2lzY28ncyBtZXRyb3BvbGl0YW4gYXJlYS4gU2FudGEgQ2xhcmEgaGFzIDkgb3BlbmluZ3MsIFJlZHdvb2QgQ2l0eSBoYXMgNywgU2FuIEpvc2UgNCwgQ3VwZXJ0aW5vIDMsIGFuZCBtYW55IG1vcmUuCgojIyMjIEF2ZXJhZ2UgU2FsYXJ5CgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpzdGF0ZXNfc2FsYXJ5IDwtIGRmICU+JQogIGdyb3VwX2J5KGpvYl9zdGF0ZSkgJT4lCiAgZHBseXI6OnN1bW1hcml6ZShgQXZlcmFnZSBTYWxhcnlgID0gbWVhbihhdmdfc2FsYXJ5KSkKIApzdGF0ZXNfc2FsYXJ5JGBBdmVyYWdlIFNhbGFyeWAgPC0gIHJvdW5kKHN0YXRlc19zYWxhcnkkYEF2ZXJhZ2UgU2FsYXJ5YCwgZGlnaXRzID0gMikKCmNpdGllc19zYWxhcnkgPC0gZGYgJT4lCiAgZ3JvdXBfYnkoTG9jYXRpb24pICU+JQogIGRwbHlyOjpzdW1tYXJpemUoYEF2ZXJhZ2UgU2FsYXJ5YCA9IG1lYW4oYXZnX3NhbGFyeSkpCgpjaXRpZXNfc2FsYXJ5JGBBdmVyYWdlIFNhbGFyeWAgPC0gIHJvdW5kKGNpdGllc19zYWxhcnkkYEF2ZXJhZ2UgU2FsYXJ5YCwgZGlnaXRzID0gMikKYGBgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpwcmludCgiVGhlIG1lZGlhbiBzYWxhcnkgaW4gdGhlIGRhdGFzZXQgaXM6IikKbWVkaWFuKGNpdGllc19zYWxhcnkkYEF2ZXJhZ2UgU2FsYXJ5YCkKYGBgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpjaXRpZXNfc2FsYXJ5IDwtIGNpdGllc19zYWxhcnkgJT4lCiAgbXV0YXRlKGBNZWRpYW5gID0gaWZlbHNlKGBBdmVyYWdlIFNhbGFyeWAgPj0gMTE1LjIsIHBhc3RlKCJBYm92ZSBOYXRpb25hbCBNZWRpYW4iKSwgcGFzdGUoIkJlbG93IE5hdGlvbmFsIE1lZGlhbiIpKSkKYGBgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30KR2VvQ29kZWQyIDwtIHB1cnJyOjptYXBfZGYoLnggPSBjaXRpZXNfc2FsYXJ5JExvY2F0aW9uLCAuZiA9IGdnbWFwOjpnZW9jb2RlKQpgYGAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cmdlb2NvZGVkX2RmMiA8LSBkcGx5cjo6YmluZF9jb2xzKGNpdGllc19zYWxhcnksIEdlb0NvZGVkMikgJT4lIAogIGRwbHlyOjpzZWxlY3QoCiAgICBsbmcgPSBsb24sCiAgICBsYXQsCiAgICBkcGx5cjo6ZXZlcnl0aGluZygpKQpgYGAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnN0YXRlc19zYWxhcnkkcG9wdXB0ZXh0IDwtIGJhc2U6OnBhc3RlMCgiPGI+IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJBdmVyYWdlIFNhbGFyeTogIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjwvYj48YnIgLz4iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGF0ZXNfc2FsYXJ5JGBBdmVyYWdlIFNhbGFyeWApCnNhbGFyeV9ieV9zdGF0ZSA8LSBtZXJnZSh1cywgc3RhdGVzX3NhbGFyeSwgYnkueCA9ICJTVFVTUFMiLCBieS55ID0gImpvYl9zdGF0ZSIpCmBgYAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZ2VvY29kZWRfZGYyJHBvcHVwdGV4dCA8LSBiYXNlOjpwYXN0ZTAoIjxiPiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ2l0eSBhdmVyYWdlIHNhbGFyeTogIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjwvYj48YnIgLz4iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW9jb2RlZF9kZjIkYEF2ZXJhZ2UgU2FsYXJ5YCkKYGBgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpiaW5zMiA8LSBjKDkwLDEwMCwxMTAsMTIwLDEzMCwxNDAsMTUwLCBJbmYpCm15X3BhbGV0dGUgPC0gY2FydG8ucGFsKCJncmVlbi5wYWwiLCA4KQpwYWwyIDwtIGNvbG9yQmluKG15X3BhbGV0dGUsIGRvbWFpbiA9IHN0YXRlc19zYWxhcnkkYEF2ZXJhZ2UgU2FsYXJ5YCwgYmlucyA9IGJpbnMyKQpgYGAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnNlY29uZF9wYWxldHRlIDwtIGMoImdyZWVuMyIsICJmaXJlYnJpY2syIikKcGFsMyA9IGNvbG9yRmFjdG9yKHNlY29uZF9wYWxldHRlLCBkb21haW4gPSBnZW9jb2RlZF9kZjIkYE1lZGlhbmApCm1lZGlhbl9jb2xvciA9IHBhbDMoZ2VvY29kZWRfZGYyJGBNZWRpYW5gKQpgYGAKCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQptYXBfc2FsYXJ5IDwtIGxlYWZsZXQoZ2VvY29kZWRfZGYyKSAlPiUKICBzZXRWaWV3KGxuZyA9IC05OC41Nzk1LCBsYXQgPSAzOS44MjgzLCB6b29tID0gMi41KSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uTm9MYWJlbHMpICU+JQogIGFkZFBvbHlnb25zKGRhdGEgPSBzYWxhcnlfYnlfc3RhdGUsIAogICAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIsIAogICAgICAgICAgICB3ZWlnaHQgPSAyLCAKICAgICAgICAgICAgc21vb3RoRmFjdG9yID0gMC41LAogICAgICAgICAgICBvcGFjaXR5ID0gMS4wLCAKICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjksCiAgICAgICAgICAgIGRhc2hBcnJheSA9ICIyIiwKICAgICAgICAgICAgZmlsbENvbG9yID0gfnBhbDIoYEF2ZXJhZ2UgU2FsYXJ5YCksCiAgICAgICAgICAgIGhpZ2hsaWdodE9wdGlvbnMgPSBoaWdobGlnaHRPcHRpb25zKGNvbG9yID0gInJlZCIsIHdlaWdodCA9IDIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyaW5nVG9Gcm9udCA9IFRSVUUpLAogICAgICAgICAgICBwb3B1cCA9IH5wb3B1cHRleHQgLAogICAgICAgICAgICBncm91cCA9ICJTdGF0ZXMiLAogICAgICAgICAgICBsYWJlbCA9IH5odG1sRXNjYXBlKGFzLmNoYXJhY3RlcihOQU1FKSkpICU+JQogIGFkZENpcmNsZU1hcmtlcnMoY29sb3IgPSB+cGFsMyhgTWVkaWFuYCksCiAgICAgICAgICAgICBsYWJlbCA9IH5odG1sRXNjYXBlKGFzLmNoYXJhY3RlcihMb2NhdGlvbikpLAogICAgICAgICAgICAgZmlsbCA9IFRSVUUsCiAgICAgICAgICAgICBsbmcgPSB+bG5nLCAKICAgICAgICAgICAgIGxhdCA9IH5sYXQsCiAgICAgICAgICAgICBwb3B1cCA9IH5wb3B1cHRleHQsCiAgICAgICAgICAgICByYWRpdXMgPSB+YEF2ZXJhZ2UgU2FsYXJ5YC8xOCwKICAgICAgICAgICAgIGdyb3VwID0gIkNpdGllcyIpICU+JQogIGFkZExlZ2VuZChwYWwgPSBwYWwzLCB2YWx1ZXMgPSB+Z2VvY29kZWRfZGYyJE1lZGlhbiwgdGl0bGUgPSAiQ2l0aWVzIHRvIE5hdGlvbmFsIE1lZGlhbiIsIGdyb3VwID0gIkNpdGllcyIpICU+JQogIGFkZExheWVyc0NvbnRyb2woCiAgICAgYmFzZUdyb3VwcyA9IGMoIkNpdGllcyIsICJTdGF0ZXMiKSwKICAgICBvcHRpb25zID0gbGF5ZXJzQ29udHJvbE9wdGlvbnMoY29sbGFwc2VkID0gRkFMU0UpCiAgKQoKbWFwX3NhbGFyeQpgYGAKCioqTm90ZTogZ3JleSBzdGF0ZXMgaGF2ZSBubyBvcGVuaW5ncyoqCgpIZXJlLCB3ZSB3YW50ZWQgdG8gZm9jdXMgbW9yZSBvbiB0aGUgY2l0aWVzLiBUaGUgY2l0aWVzIHRoYXQgaGF2ZSBhbiBhdmVyYWdlIHNhbGFyeSBmb3IgdGhlaXIgb3BlbmluZ3MgaGlnaGVyIHRoYW4gdGhlIG5hdGlvbmFsIG1lZGlhbiBhcmUgY29sb3JlZCBpbiBncmVlbi4gT24gdGhlIG90aGVyIGhhbmQsIGNpdGllcyB3aXRoIGF2ZXJhZ2Ugc2FsYXJpZXMgYmVsb3cgdGhlIG5hdGlvbmFsIG1lZGlhbiBhcmUgY29sb3JlZCBpbiByZWQuIEZ1cnRoZXJtb3JlLCB0aGUgc2l6ZSBvZiB0aGUgY2lyY2xlcyBpcyBwcm9wb3J0aW9uYWwgdG8gdGhlIGNpdHkncyBhdmVyYWdlIHNhbGFyeS4gQW1vbmcgdGhlIG1vc3Qga25vd24gY2l0aWVzLCBhbmQgd2l0aCBhIGRpc2NyZXRlIG51bWJlciBvZiBvcGVuaW5ncywgRGFsbGFzIChUWCkgYW5kIFNhY3JhbWVudG8gKENBKSBhcmUgdGhlIHR3byB3aXRoIHRoZSBoaWdoZXN0IGF2ZXJhZ2Ugc2FsYXJ5OiBgJGAxODMsMDAwLiBUaGVyZSBhcmUgdGhyZWUgY2l0aWVzIHdpdGggYSBzYWxhcnkgb2YgYCRgMjcxLDAwMCBhbmQgb25seSBvbmUgam9iIG9wZW5pbmcuIE9uZSBpbiBUWCwgb25lIGluIENBIGFuZCBvbmUgaW4gREUuIENvbG9yYWRvIFNwcmluZ3MgKENPKSBhbmQgVHVsc2EgKE9LKSBjbG9zZSB0aGUgbGlzdCB3aXRoIHNhbGFyaWVzIG9mIGp1c3QgNDNrIGFuZCA2N2suCgpPbiB0aGUgb3RoZXIgaGFuZCwgdGhlIHN0YXRlcyBtYXAgc2hvd3MgdGhlIGF2ZXJhZ2Ugc2FsYXJ5IHBlciBzdGF0ZS4gVGhlIHN0YXRlIHdpdGggdGhlIGhpZ2hlciBhdmVyYWdlIGlzIERlbGF3YXJlICh3aXRoIGAkYDI3MSwwMDApLCBidXQgdGhpcyBpcyBiZWNhdXNlIGl0IGhhcyBqdXN0IG9uZSBvcGVuaW5nLiBJbiB0aGUgc2Vjb25kIHBsYWNlIGNvbWVzIE5vcnRoIENhcm9saW5hIHdpdGgganVzdCBzbGlnaHRseSB1bmRlciBgJGAxNTAsMDAwLiBTb3V0aCBDYXJvbGluYSBhbmQgTW9udGFuYSBjbG9zZSB0aGUgbGlzdCB3aXRoLCByZXNwZWN0aXZlbHksIDk1LjVrIGFuZCA5My43NWsuCgojIyMgVGV4dCBBbmFseXNpcyBvZiBEYXRhIFNjaWVuY2UgSm9iIERlc2NyaXB0aW9ucwoKRm9sbG93aW5nIG91ciBhbmFseXNpcyBvZiBqb2Igb3BlbmluZ3MgYW5kIHNhbGFyaWVzLCB3ZSB3YW50ZWQgdG8gYW5hbHl6ZSB0aGUgZGVzY3JpcHRpb25zIG9mIHRoZSBqb2Igb3BlbmluZ3MgdG8gYmV0dGVyIHVuZGVyc3RhbmQgd2hhdCBza2lsbHMgYW5kIGFiaWxpdGllcyBhcmUgZGVzaXJlZCBieSBwb3RlbnRpYWwgZW1wbG95ZXJzLiBXZSBhY2hpZXZlIHRoaXMgZ29hbCBieSBicmVha2luZyBkb3duIHNwZWNpZmljIHdvcmRzIHRoYXQgY29tbW9ubHkgc2hvdyB1cCBpbiBkYXRhIHNjaWVuY2Ugam9iIGRlc2NyaXB0aW9ucywgd2l0aCBhIHNwZWNpYWwgZm9jdXMgb24gd29yZHMgdGhhdCBpbmRpY2F0ZSBhIHNwZWNpZmljIHNraWxsIHNldCBvZiB0aGUgYXBwbGljYW50cy4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgQWRkIFVuaXF1ZSBJRCBmb3IgZWFjaCByb3cKZGYkaWQgPC0gaWQoZGYpCmBgYAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBDbGVhbiB1cCBkYXRhIHRvIHByZXBhcmUgZm9yIHRleHQgYW5hbHlzaXMKZGYkSm9iLkRlc2NyaXB0aW9uIDwtIHJlcGxhY2VfdXJsKGRmJEpvYi5EZXNjcmlwdGlvbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGF0dGVybiA9IHFkYXBSZWdleDo6Z3JhYigicm1fdXJsIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcGxhY2VtZW50ID0gIiIpCmRmJEpvYi5EZXNjcmlwdGlvbiA8LSBzdHJfcmVwbGFjZV9hbGwoZGYkSm9iLkRlc2NyaXB0aW9uLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGF0dGVybiA9ICJbWzpkaWdpdDpdXSsiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwbGFjZW1lbnQgPSAiIikKZGYkSm9iLkRlc2NyaXB0aW9uIDwtIHN0cl9yZXBsYWNlX2FsbChkZiRKb2IuRGVzY3JpcHRpb24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXR0ZXJuID0gIltbOnB1bmN0Ol1dKyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXBsYWNlbWVudCA9ICIiKQpkZiRKb2IuRGVzY3JpcHRpb24gPC0gdHJpbShkZiRKb2IuRGVzY3JpcHRpb24pCmBgYAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZGZfdW5uZXN0IDwtIHVubmVzdF90b2tlbnMoZGYsIHdvcmQsIEpvYi5EZXNjcmlwdGlvbikKYGBgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIHJlbW92ZSBzdG9wIHdvcmRzCmRmX3VubmVzdCA8LSBhbnRpX2pvaW4oZGZfdW5uZXN0LCBzdG9wX3dvcmRzLCAKICAgICAgICAgICAgICAgICAgICAgICBieSA9ICJ3b3JkIikKYGBgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIG9ubHkga2VlcCB1c2VmdWwgd29yZHMKZGZfdW5uZXN0IDwtIGlubmVyX2pvaW4oZGZfdW5uZXN0LCBrZXlfd29yZHMsIAogICAgICAgICAgICAgICAgICAgICAgICBieSA9ICJ3b3JkIikKYGBgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQood29yZF9jb3VudCA8LSBkZl91bm5lc3QgJT4lIGdyb3VwX2J5KHdvcmQsIHdvcmRfdHlwZSkgJT4lIAogIHN1bW1hcmlzZShjb3VudCA9IG4oKSwgYXZnX3NhbGFyeSA9IHJvdW5kKChtZWFuKGF2Z19zYWxhcnkpKSwgZGlnaXRzID0gMSkpKSAlPiUKICBhcnJhbmdlKGRlc2MoY291bnQpKQpgYGAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgQ3JlYXRlIGEgd29yZGNsb3VkIG9mIHdvcmQgY291bnQgZm9yIHNraWxscwpza2lsbHNfd29yZF9jb3VudCA8LSBmaWx0ZXIod29yZF9jb3VudCwgd29yZF90eXBlID09ICJza2lsbCIpCndvcmRjbG91ZChza2lsbHNfd29yZF9jb3VudCR3b3JkLCBza2lsbHNfd29yZF9jb3VudCRjb3VudCwKICAgICAgICAgIG1heC53b3JkcyA9IDEwMCwgY29sb3JzID0gInBpbmszIikKYGBgCgpUaGUgd29yZCBjbG91ZCBhYm92ZSBzaG93cyB0aGUgbW9zdCBjb21tb24gc2tpbGxzIG1lbnRpb25lZCBpbiBkYXRhIHNjaWVuY2Ugam9iIGRlc2NyaXB0aW9ucy4gU29tZSBvZiB0aGUgbW9zdCBjb21tb24gYXJlIG9idmlvdXMsIGxpa2UgbWFjaGluZSBsZWFybmluZywgbW9kZWxpbmcsIGtub3dpbmcgZGF0YSBsYW5ndWFnZXMgYW5kIGhvdyB0byB1c2UgY2VydGFpbiBhcHBsaWNhdGlvbnMuIFNvbWUgb2YgdGhlIG1vc3QgY29tbW9uIGFyZSBhIGxpdHRsZSBtb3JlIHN1cnByaXNpbmcsIGxpa2UgaGF2aW5nIHN0cm9uZyB3cml0aW5nIGFuZCBjb21tdW5pY2F0aW9uIHNraWxscywgb3IgYmVpbmcgYSBnb29kIGNvbGxhYm9yYXRvci4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CiMgQ3JlYXRlIHRlcm0gZG9jdW1lbnQgbWF0cml4IHRvIGNvbXBhcmUgbW9zdCBjb21tb24gc2tpbGxzIGFuZCB0b29scyBpbiBkYXRhIHNjaWVudGlzdCB2ZXJzdXMgZGF0YSBhbmFseXN0IGpvYiBwb3N0aW5ncwphbmFseXN0X3NjaV90ZG0gPC0gZGZfdW5uZXN0ICU+JSBmaWx0ZXIod29yZF90eXBlICE9ICJ0YXNrIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEpvYi5UaXRsZSA9PSAiRGF0YSBTY2llbnRpc3QiIHwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEpvYi5UaXRsZSA9PSAiRGF0YSBBbmFseXN0IikgJT4lIGdyb3VwX2J5KEpvYi5UaXRsZSwgd29yZCkgJT4lIHN1bW1hcmlzZShjb3VudCA9IG4oKSkgJT4lIGNhc3RfdGRtKHdvcmQsIEpvYi5UaXRsZSwgY291bnQpCmBgYAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KYW5hbHlzdF9zY2lfbWF0cml4IDwtIGFzLm1hdHJpeChhbmFseXN0X3NjaV90ZG0pCmBgYAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBDcmVhdGUgY29tcGFyaXNvbiBjbG91ZCBmb3Igc2tpbGxzIGFuZCB0b29scyBpbiBkYXRhIHNjaWVuY2UgdnMuIGRhdGEgYW5hbHlzdCBqb2JzCmNvbXBhcmlzb24uY2xvdWQoYW5hbHlzdF9zY2lfbWF0cml4LCBjb2xvcnMgPSBjKCJzYWxtb24iLCAiZGFya3NsYXRlZ3JheTQiKSwgdGl0bGUuc2l6ZT0gMiwgCiAgICAgICAgICAgICAgICAgbWF4LndvcmRzID0gMTAwLCB0aXRsZS5iZy5jb2xvcnMgPSAicGVhY2hwdWZmMiIpCmBgYAoKVGhlIGNvbXBhcmlzb24gY2xvdWQgYWJvdmUgc2hvd3MgdGhlIG1vc3QgY29tbW9uIHNraWxscyBhbmQgdG9vbHMgbWVudGlvbmVkIGluIGRhdGEgc2NpZW50aXN0IHZlcnN1cyBkYXRhIGFuYWx5c3Qgam9iIGRlc2NyaXB0aW9ucy4gVGhlIHdvcmRzIGluIHRoZSBkYXRhIHNjaWVudGlzdCBqb2IgZGVzY3JpcHRpb25zIGFwcGVhciB0byBiZSBtdWNoIG1vcmUgdGVjaG5pY2FsLCBzdWNoIGFzIG1hY2hpbmUgbGVhcm5pbmcsIGVuZ2luZWVyaW5nLCBhbmQgbW9kZWxpbmcsIHdoaWxlIHdvcmRzIGluIGRhdGEgYW5hbHlzdCBqb2IgZGVzY3JpcHRpb25zIGFyZSBtdWNoIG1vcmUgZm9jdXNlZCBhcm91bmQgdGhlIHVzZSBvZiBzcGVjaWZpYyB0b29scyB1c2VkIGZvciBkYXRhIGFuYWx5c2lzIChzdWNoIGFzIE1pY3Jvc29mdCBhbmQgVGFibGVhdSkgYW5kIGNvbW11bmljYXRpbmcgaW5zaWdodHMgdGhyb3VnaCBkYXNoYm9hcmRzIGFuZCBwcmVzZW50YXRpb25zLgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KIyBDcmVhdGUgdGVybSBkb2N1bWVudCBtYXRyaXggdG8gY29tcGFyZSBtb3N0IGNvbW1vbiBza2lsbHMgYW5kIHRvb2xzIGluIGRhdGEgc2NpZW50aXN0IHZlcnN1cyBkYXRhIGFuYWx5c3Qgam9iIHBvc3RpbmdzCnRvb2xfdGRtIDwtIGRmX3VubmVzdCAlPiUgZmlsdGVyKHdvcmRfdHlwZSA9PSAidG9vbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBKb2IuVGl0bGUgPT0gIkRhdGEgU2NpZW50aXN0IiB8CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBKb2IuVGl0bGUgPT0gIkRhdGEgQW5hbHlzdCIpICU+JSBncm91cF9ieShKb2IuVGl0bGUsIHdvcmQpICU+JSBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JSBjYXN0X3RkbSh3b3JkLCBKb2IuVGl0bGUsIGNvdW50KQpgYGAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CnRvb2xfbWF0cml4IDwtIGFzLm1hdHJpeCh0b29sX3RkbSkKYGBgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIENyZWF0ZSBjb21wYXJpc29uIGNsb3VkIGZvciB0b29scyBpbiBkYXRhIHNjaWVuY2UgdnMuIGRhdGEgYW5hbHlzdCBqb2JzCmNvbXBhcmlzb24uY2xvdWQodG9vbF9tYXRyaXgsIGNvbG9ycyA9IGMoInNhbG1vbiIsICJkYXJrc2xhdGVncmF5NCIpLCB0aXRsZS5zaXplPSAyLCAKICAgICAgICAgICAgICAgICBtYXgud29yZHMgPSAxMDAsIHRpdGxlLmJnLmNvbG9ycyA9ICJwZWFjaHB1ZmYyIikKYGBgCgpUaGUgY29tcGFyaXNvbiBjbG91ZCBhYm92ZSBzaG93cyB0aGUgbW9zdCBjb21tb24gdG9vbHMgbWVudGlvbmVkIGluIGRhdGEgc2NpZW50aXN0IHZlcnN1cyBkYXRhIGFuYWx5c3Qgam9iIGRlc2NyaXB0aW9ucy4gT25jZSBhZ2Fpbiwgd2UgY2FuIHNlZSB0aGF0IGRhdGEgc2NpZW50aXN0IHJvbGVzIGFwcGVhciB0byBiZSBtdWNoIG1vcmUgdGVjaG5pY2FsIGFuZCBvcmllbnRlZCBhcm91bmQgY29kaW5nIGZvciBkYXRhIGFuYWx5c2lzLCB3aXRoIHRvb2xzIHN1Y2ggYXMgUHl0aG9uLCBTQVMgYW5kIEphdmEgdG9wcGluZyB0aGUgbGlzdCwgd2hpbGUgZGF0YSBhbmFseXNpcyB0b29scyAod2l0aCB0aGUgZXhjZXB0aW9uIG9mIFNRTCkgYXJlIG1vcmUgZm9jdXNlZCBhcm91bmQgZ2VuZXJhbCBidXNpbmVzcyBpbnRlbGxpZ2VuY2UgdG9vbHMgdXNlZCBmb3IgbGVzcyBhZHZhbmNlZCBhbmFseXNpcywgc3VjaCBhcyBFeGNlbCwgVGFibGVhdSwgYW5kIE1pY3Jvc29mdC4KCgojIyMgR2VuZGVyIFBheSBHYXAgaW4gRGF0YSBTY2llbmNlIEpvYnMKCkluIHRoaXMgcGFydCBvZiB0aGUgcHJvamVjdCwgd2Ugd291bGQgbGlrZSB0byBmb2N1cyBvbiB0aGUgZ2VuZGVyIHBheSBnYXAgYW1vbmcgRGF0YSBTY2llbnRpc3QgaW4gdGhlIFVTLiBVbmZvcnR1bmF0ZWx5LCBpdCBoYXMgYmVlbiBbc3R1ZGllZF0oaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL2dlbmRlci1wYXktZ2FwLWFtb25nLWRhdGEtc2NpZW50aXN0cy1vbi1rYWdnbGUtODdiMzkzYWEyMWZlKSB0aGF0IG1hbGUgZGF0YSBzY2llbnRpc3RzIGluIHRoZSBVUyBlYXJuIG1vcmUgYW5kIGhhdmUgYSBoaWdoZXIgZWFybmluZ3MgcmFuZ2UgdGhhbiB0aGVpciBmZW1hbGUgY291bnRlcnBhcnRzLiBGdXJ0aGVybW9yZSwgaXQgaGFzIGJlZW4gW2RlbW9uc3RyYXRlZF0oaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9kNDE1ODYtMDE5LTAwNjExLTEpIHRoYXQgbW9yZSB0aGFuIDQwJSBvZiB3b21lbiB3aXRoIGZ1bGwtdGltZSBqb2JzIGluIHNjaWVuY2UgbGVhdmUgdGhlIHNlY3RvciBvciBnbyBwYXJ0IHRpbWUgYWZ0ZXIgaGF2aW5nIHRoZWlyIGZpcnN0IGNoaWxkIGluIHRoZSBVbml0ZWQgU3RhdGVzLiBCeSBjb250cmFzdCwgb25seSAyMyUgb2YgbmV3IGZhdGhlcnMgbGVhdmUgb3IgY3V0IHRoZWlyIHdvcmtpbmcgaG91cnMuIFdlIHdvdWxkIGxpa2UgdG8gc2VlIGlmIHRoZXNlIGZpbmRpbmdzIGFyZSBhbHNvIHN1cHBvcnRlZCBieSB0aGUgZGF0YSB0aGF0IHdlIGhhdmUuIFdlIGFyZSBnb2luZyB0byB1c2UgdGhlIDIwMTggS2FnZ2xlIE1hY2hpbmUgTGVhcm5pbmcgJiBEYXRhIFNjaWVuY2UgU3VydmV5LCB0aGUgbW9zdCBjb21wcmVoZW5zaXZlIGRhdGFzZXQgYXZhaWxhYmxlIG9uIHRoZSBzdGF0ZSBvZiBNTCBhbmQgZGF0YSBzY2llbmNlLgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kc3VydmV5IDwtIHJlYWQuY3N2KCJHZW5kZXIgZ2FwIGluIERhdGEgU2NpZW5jZS9tdWx0aXBsZUNob2ljZVJlc3BvbnNlcy5jc3YiKQpgYGAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnN1cnZleSA8LSBzdXJ2ZXkgJT4lIGZpbHRlcihRMyA9PSAiVW5pdGVkIFN0YXRlcyBvZiBBbWVyaWNhIiB8IFEzID09ICJJbiB3aGljaCBjb3VudHJ5IGRvIHlvdSBjdXJyZW50bHkgcmVzaWRlPyIpCmBgYAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kc3VydmV5X3NhbGFyeSA8LSBzdXJ2ZXkgJT4lCiAgZmlsdGVyKFE5ICE9ICJJIGRvIG5vdCB3aXNoIHRvIGRpc2Nsb3NlIG15IGFwcHJveGltYXRlIHllYXJseSBjb21wZW5zYXRpb24iKSAKYGBgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpzdXJ2ZXlfc2FsYXJ5JG1pbiA8LSBzdHJfZXh0cmFjdChzdXJ2ZXlfc2FsYXJ5JFE5LCAiW14tXSsiKQpzdXJ2ZXlfc2FsYXJ5JG1heCA8LSBnc3ViKCAiKC4qKS0oLiopIiwgIlxcMiIsICBzdXJ2ZXlfc2FsYXJ5JFE5KQpzdXJ2ZXlfc2FsYXJ5JG1heDEgPC0gc3RyX2V4dHJhY3Qoc3VydmV5X3NhbGFyeSRtYXgsICJbXixdKyIpCmBgYAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kc3VydmV5X3NhbGFyeSRtaW4gPC0gYXMubnVtZXJpYyhzdXJ2ZXlfc2FsYXJ5JG1pbikKc3VydmV5X3NhbGFyeSRtYXgxIDwtIGFzLm51bWVyaWMoc3VydmV5X3NhbGFyeSRtYXgxKQpzdXJ2ZXlfc2FsYXJ5IDwtIHN1cnZleV9zYWxhcnkgJT4lIG11dGF0ZShhdmVyYWdlX3NhbGFyeSA9ICgoKG1pbiArIG1heDEpLzIpKjEwMDApKQpgYGAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnN1cnZleV9zYWxhcnkkUTFbc3VydmV5X3NhbGFyeSRRMT09IlByZWZlciBub3QgdG8gc2F5Il0gPC0gIk90aGVyIgpzdXJ2ZXlfc2FsYXJ5JFExW3N1cnZleV9zYWxhcnkkUTE9PSJQcmVmZXIgdG8gc2VsZi1kZXNjcmliZSJdIDwtICJPdGhlciIKc3VydmV5X3NhbGFyeSA8LSBzdXJ2ZXlfc2FsYXJ5JT4lCiAgZmlsdGVyKFEzICE9ICJJbiB3aGljaCBjb3VudHJ5IGRvIHlvdSBjdXJyZW50bHkgcmVzaWRlPyIpCmBgYAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KR2VvbVNwbGl0VmlvbGluIDwtIGdncHJvdG8oCiAgIkdlb21TcGxpdFZpb2xpbiIsIAogIEdlb21WaW9saW4sIAogIGRyYXdfZ3JvdXAgPSBmdW5jdGlvbihzZWxmLCBkYXRhLCAuLi4sIGRyYXdfcXVhbnRpbGVzID0gTlVMTCkgewogICAgZGF0YSA8LSB0cmFuc2Zvcm0oZGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICB4bWludiA9IHggLSB2aW9saW53aWR0aCAqICh4IC0geG1pbiksIAogICAgICAgICAgICAgICAgICAgICAgeG1heHYgPSB4ICsgdmlvbGlud2lkdGggKiAoeG1heCAtIHgpKQogICAgZ3JwIDwtIGRhdGFbMSwnZ3JvdXAnXQogICAgbmV3ZGF0YSA8LSBwbHlyOjphcnJhbmdlKAogICAgICB0cmFuc2Zvcm0oZGF0YSwgeCA9IGlmKGdycCUlMj09MSkgeG1pbnYgZWxzZSB4bWF4diksIAogICAgICBpZihncnAlJTI9PTEpIHkgZWxzZSAteQogICAgKQogICAgbmV3ZGF0YSA8LSByYmluZChuZXdkYXRhWzEsIF0sIG5ld2RhdGEsIG5ld2RhdGFbbnJvdyhuZXdkYXRhKSwgXSwgbmV3ZGF0YVsxLCBdKQogICAgbmV3ZGF0YVtjKDEsbnJvdyhuZXdkYXRhKS0xLG5yb3cobmV3ZGF0YSkpLCAneCddIDwtIHJvdW5kKG5ld2RhdGFbMSwgJ3gnXSkgCiAgICBpZiAobGVuZ3RoKGRyYXdfcXVhbnRpbGVzKSA+IDAgJiAhc2NhbGVzOjp6ZXJvX3JhbmdlKHJhbmdlKGRhdGEkeSkpKSB7CiAgICAgIHN0b3BpZm5vdChhbGwoZHJhd19xdWFudGlsZXMgPj0gMCksIGFsbChkcmF3X3F1YW50aWxlcyA8PSAxKSkKICAgICAgcXVhbnRpbGVzIDwtIGdncGxvdDI6OjpjcmVhdGVfcXVhbnRpbGVfc2VnbWVudF9mcmFtZShkYXRhLCBkcmF3X3F1YW50aWxlcykKICAgICAgYWVzdGhldGljcyA8LSBkYXRhW3JlcCgxLCBucm93KHF1YW50aWxlcykpLCBzZXRkaWZmKG5hbWVzKGRhdGEpLCBjKCJ4IiwgInkiKSksIGRyb3AgPSBGQUxTRV0KICAgICAgYWVzdGhldGljcyRhbHBoYSA8LSByZXAoMSwgbnJvdyhxdWFudGlsZXMpKQogICAgICBib3RoIDwtIGNiaW5kKHF1YW50aWxlcywgYWVzdGhldGljcykKICAgICAgcXVhbnRpbGVfZ3JvYiA8LSBHZW9tUGF0aCRkcmF3X3BhbmVsKGJvdGgsIC4uLikKICAgICAgZ2dwbG90Mjo6OmdnbmFtZSgiZ2VvbV9zcGxpdF92aW9saW4iLCAKICAgICAgICAgICAgICAgICAgICAgICBncmlkOjpncm9iVHJlZShHZW9tUG9seWdvbiRkcmF3X3BhbmVsKG5ld2RhdGEsIC4uLiksIHF1YW50aWxlX2dyb2IpKQogICAgfSBlbHNlIHsKICAgICAgZ2dwbG90Mjo6OmdnbmFtZSgiZ2VvbV9zcGxpdF92aW9saW4iLCBHZW9tUG9seWdvbiRkcmF3X3BhbmVsKG5ld2RhdGEsIC4uLikpCiAgICB9CiAgfQopCgpnZW9tX3NwbGl0X3Zpb2xpbiA8LSBmdW5jdGlvbiAobWFwcGluZyA9IE5VTEwsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IE5VTEwsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhdCA9ICJ5ZGVuc2l0eSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSAiaWRlbnRpdHkiLCAuLi4sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHJhd19xdWFudGlsZXMgPSBOVUxMLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyaW0gPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlID0gImFyZWEiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hLnJtID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBOQSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmhlcml0LmFlcyA9IFRSVUUpIHsKICBsYXllcihkYXRhID0gZGF0YSwgCiAgICAgICAgbWFwcGluZyA9IG1hcHBpbmcsIAogICAgICAgIHN0YXQgPSBzdGF0LCAKICAgICAgICBnZW9tID0gR2VvbVNwbGl0VmlvbGluLCAKICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uLCAKICAgICAgICBzaG93LmxlZ2VuZCA9IHNob3cubGVnZW5kLCAKICAgICAgICBpbmhlcml0LmFlcyA9IGluaGVyaXQuYWVzLCAKICAgICAgICBwYXJhbXMgPSBsaXN0KHRyaW0gPSB0cmltLCAKICAgICAgICAgICAgICAgICAgICAgIHNjYWxlID0gc2NhbGUsIAogICAgICAgICAgICAgICAgICAgICAgZHJhd19xdWFudGlsZXMgPSBkcmF3X3F1YW50aWxlcywgCiAgICAgICAgICAgICAgICAgICAgICBuYS5ybSA9IG5hLnJtLCAuLi4pCiAgICAgICAgKQp9CgpzdXJ2ZXlfc2FsYXJ5ICU+JQogIGZpbHRlcihRMSAhPSAiT3RoZXIiKSAlPiUKICBmaWx0ZXIoUTQgJWluJSBjKCJEb2N0b3JhbCBkZWdyZWUiLCAiTWFzdGVy4oCZcyBkZWdyZWUiKSkgICU+JQogIGdncGxvdChhZXMoeCA9IFExLCB5ID0gYXZlcmFnZV9zYWxhcnksIGZpbGwgPSBRNCkpICsKICBnZW9tX3NwbGl0X3Zpb2xpbih0cmltPUZBTFNFKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSB3ZXNfcGFsZXR0ZSgyLCBuYW1lID0gIkdyYW5kQnVkYXBlc3QyIikpICsKICBsYWJzKHggPSAiR2VuZGVyIiwgeSA9ICJBdmVyYWdlIHllYXJseSBzYWxhcnkiLCB0aXRsZSA9ICJBdmVyYWdlIERhdGEgU2NpZW50aXN0cycgU2FsYXJ5IGJ5IEVkdWNhdGlvbmFsIExldmVsIGFuZCBHZW5kZXIiLCBjYXB0aW9uID0gIlNvdXJjZTogMjAxOCBLYWdnbGUgTWFjaGluZSBMZWFybmluZyAmIERhdGEgU2NpZW5jZSBTdXJ2ZXkiLCBmaWxsID0gIkVkdWNhdGlvbmFsIGxldmVsIikgKwogIHRoZW1lKHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChoanVzdCA9IC0wLjQsIHZqdXN0ID0gLTIsc2l6ZSA9IDgsIGNvbG9yID0iYXp1cmU0IiksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMSwgdmp1c3QgPSAzKSwKICAgICAgICBheGlzLnRpdGxlLng9ZWxlbWVudF90ZXh0KHZqdXN0ID0gLTAuNykpCmBgYAoKVGhlIGFib3ZlIGdyYXBoIHNob3dzIHRoZSBhdmVyYWdlIERhdGEgU2NpZW50aXN0cycgc2FsYXJ5IGJ5IGVkdWNhdGlvbmFsIGxldmVsIGFuZCBnZW5kZXIuIEZpcnN0IG9mIGFsbCwgaXQgY2FuIGJlIHNlZW4gdGhhdCwgaGF2aW5nIGEgRG9jdG9yYWwgRGVncmVlIGluc3RlYWQgb2YgYSBNYXN0ZXIncyBEZWdyZWUsIGFsbG93cyBwZW9wbGUgdG8gZWFybiBtb3JlLCBpbmRlcGVuZGVudGx5IG9mIHRoZWlyIHNleC4gSG93ZXZlciwgaXQgY2FuIGJlIHNlZW4gdGhhdCB3b21lbiB3aXRoIGJvdGggYSBEb2N0b3JhbCBhbmQgYSBNYXN0ZXIncyBEZWdyZWUgZWFybiBsZXNzIHRoYW4gbWVuLiBBbHNvLCB0aGUgZWFybmluZ3MgZm9yIG1lbiBhcmUgbW9yZSBzcHJlYWQgb3V0IGFuZCBjYW4gcmVhY2ggJDUwMCwwMDAgcGVyIHllYXIsIHdoaWxlIHRoaXMgZG9lcyBub3QgaGFwcGVuIGZvciB3b21lbi4gRnJvbSB0aGlzIGdyYXBoLCBpdCBjYW4gYmUgY29uY2x1ZGVkIHRoYXQgYSBnZW5kZXIgcGF5IGdhcCBhbW9uZyBEYXRhIFNjaWVudGlzdCBpbiB0aGUgVVMgYWN0dWFsbHkgZXhpc3RzLgoKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnN1cnZleV9zYWxhcnkkUTJbc3VydmV5X3NhbGFyeSRRMj09IjE4LTIxIl0gPC0gIjE4LTI0IgpzdXJ2ZXlfc2FsYXJ5JFEyW3N1cnZleV9zYWxhcnkkUTI9PSIyMi0yNCJdIDwtICIxOC0yNCIKc3VydmV5X3NhbGFyeSRRMltzdXJ2ZXlfc2FsYXJ5JFEyPT0iNzAtNzkiXSA8LSAiNzArIgpzdXJ2ZXlfc2FsYXJ5JFEyW3N1cnZleV9zYWxhcnkkUTI9PSI4MCsiXSA8LSAiNzArIgpgYGAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiNUQUtFIE9USEVSIE9VVAojQUxTTyBOT1QgTUFOWSBTRU5JT1IgREFUQSBTQ0lFTlRJU1RTCiNDSEFOR0UgVE8gR0VPTV9CQVIKCnN1cnZleV9zYWxhcnkgJT4lCiAgZmlsdGVyKFExICE9ICJPdGhlciIpICU+JQogIGdncGxvdChhZXMoeCA9IFEyLCB5ID0gYXZlcmFnZV9zYWxhcnkpKSArCiAgZ2VvbV9iYXIoYWVzKGZpbGwgPSBRMSksIHN0YXQ9ImlkZW50aXR5IiwgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSgpKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBsYWJzKHggPSAiQWdlIiwgeSA9ICJBdmVyYWdlIHllYXJseSBzYWxhcnkiLCB0aXRsZSA9ICJBdmVyYWdlIERhdGEgU2NpZW50aXN0cycgU2FsYXJ5IGJ5IEFnZSBhbmQgR2VuZGVyIiwgY2FwdGlvbiA9ICJTb3VyY2U6IDIwMTggS2FnZ2xlIE1hY2hpbmUgTGVhcm5pbmcgJiBEYXRhIFNjaWVuY2UgU3VydmV5IiwgZmlsbCA9ICJHZW5kZXIiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygicm9zeWJyb3duMSIsICJjYWRldGJsdWUyIikpICsKICB0aGVtZShwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoaGp1c3QgPSAtMC4zLCB2anVzdCA9IC0yLjMsc2l6ZSA9IDgsIGNvbG9yID0iYXp1cmU0IiksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMSwgdmp1c3QgPSAzKSwKICAgICAgICBheGlzLnRpdGxlLng9ZWxlbWVudF90ZXh0KHZqdXN0ID0gLTAuNykpCmBgYAoKVGhlIGFib3ZlIGdyYXBoIHNob3dzIHRoZSBhdmVyYWdlIERhdGEgU2NpZW50aXN0cycgc2FsYXJ5IGJ5IGFnZSBhbmQgZ2VuZGVyLiBJdCBjYW4gYmUgc2VlbiB0aGF0LCBhdCB0aGUgYmVnaW5uaW5nIG9mIHRoZWlyIHdvcmtpbmcgY2FyZWVyLCBtZW4gYW5kIHdvbWVuIGVhcm4gYWxtb3N0IHRoZSBzYW1lLiBIb3dldmVyLCB3aGVuIHRoZSBhZ2UgaW5jcmVhc2VzLCBhdCBhYm91dCAzMCB5ZWFycyBvbGQsIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIHNhbGFyeSBmb3IgbWVuIHZzIHRoZSBvbmUgZm9yIHdvbWVuIGlzIGV2aWRlbnQuIFRoaXJ0eSB5ZWFycyBpcyB0aGUgYWdlIHdoZW4gYSB3b21hbiB1c3VhbGx5IGJlY29tZXMgYSBtb3RoZXIgYW5kLCBwcmVjaXNlbHkgaW4gY29uanVuY3Rpb24gd2l0aCB0aGlzIGFnZSBncm91cCwgdGhlIGRpZmZlcmVuY2VzIGJldHdlZW4gdGhlIG1hbGUgYW5kIGZlbWFsZSB3YWdlcyBhcmUgY2xlYXIuIE1lbiBlYXJuIGFsbW9zdCB0d2ljZSBhcyBtdWNoIGFzIHdvbWVuLiBBcyB0aGUgYXJ0aWNsZSBieSBOYXR1cmUgc3VnZ2VzdHMsICJwYXJlbnRob29kIGlzIGFuIGltcG9ydGFudCBkcml2ZXIgb2YgZ2VuZGVyIGltYmFsYW5jZSBpbiBTVEVNIGVtcGxveW1lbnQuIiBJbiBjb25jbHVzaW9uLCB3ZSBob3BlIHRoYXQgdGhlIGdhcCB3aWxsIGNsb3NlIGluIHRoZSBmdXR1cmUuCgpJbiB0aGUgc2FtZSByZXBvc2l0b3J5LCB5b3UgY2FuIGZpbmQgYSBbcHJvY2VzcyBib29rXShodHRwczovL2dpdGh1Yi5jb20vUU1TUy1HNTA2My0yMDIxL0dyb3VwQV9EYXRhX1NjaWVuY2VfSm9icy9ibG9iL21haW4vUFJPQ0VTUyUyMEJPT0sucGRmKSB3aXRoIGEgZGVzY3JpcHRpb24gb2YgdGhlIHN0ZXBzIHRha2VuIHRvIGNsZWFuIHRoZSBkYXRhIGFuZCBjcmVhdGUgb3VyIGdyYXBocy4KCgoKCgoKCgoKCgoKCgoKCgoKCgoK